diff --git a/.envrc b/.envrc deleted file mode 100644 index 3550a30..0000000 --- a/.envrc +++ /dev/null @@ -1 +0,0 @@ -use flake diff --git a/.github/workflows/build_nix.yml b/.github/workflows/build_nix.yml deleted file mode 100644 index 2f684e1..0000000 --- a/.github/workflows/build_nix.yml +++ /dev/null @@ -1,13 +0,0 @@ -name: "Build legacy Nix package on Ubuntu" - -on: - push: - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: cachix/install-nix-action@v12 - - name: Building package - run: nix-build . -A defaultPackage.x86_64-linux diff --git a/.github/workflows/gh-release.yml b/.github/workflows/gh-release.yml deleted file mode 100644 index 66c88c8..0000000 --- a/.github/workflows/gh-release.yml +++ /dev/null @@ -1,154 +0,0 @@ -name: GitHub Release - -on: - push: - tags: - - v[0-9]+.[0-9]+.[0-9]+* - -jobs: - release: - name: Publish to Github Releases - outputs: - rc: ${{ steps.check-tag.outputs.rc }} - strategy: - matrix: - include: - - target: aarch64-unknown-linux-musl - os: ubuntu-latest - use-cross: true - cargo-flags: "" - - target: aarch64-apple-darwin - os: macos-latest - use-cross: true - cargo-flags: "" - - target: aarch64-pc-windows-msvc - os: windows-latest - use-cross: true - cargo-flags: "--no-default-features" - - target: x86_64-apple-darwin - os: macos-latest - cargo-flags: "" - - target: x86_64-pc-windows-msvc - os: windows-latest - cargo-flags: "" - - target: x86_64-unknown-linux-musl - os: ubuntu-latest - use-cross: true - cargo-flags: "" - - target: i686-unknown-linux-musl - os: ubuntu-latest - use-cross: true - cargo-flags: "" - - target: i686-pc-windows-msvc - os: windows-latest - use-cross: true - cargo-flags: "" - - target: armv7-unknown-linux-musleabihf - os: ubuntu-latest - use-cross: true - cargo-flags: "" - - target: arm-unknown-linux-musleabihf - os: ubuntu-latest - use-cross: true - cargo-flags: "" - - target: mips-unknown-linux-musl - os: ubuntu-latest - use-cross: true - cargo-flags: "--no-default-features" - - target: mipsel-unknown-linux-musl - os: ubuntu-latest - use-cross: true - cargo-flags: "--no-default-features" - - target: mips64-unknown-linux-gnuabi64 - os: ubuntu-latest - use-cross: true - cargo-flags: "--no-default-features" - - target: mips64el-unknown-linux-gnuabi64 - os: ubuntu-latest - use-cross: true - cargo-flags: "--no-default-features" - runs-on: ${{matrix.os}} - - steps: - - uses: actions/checkout@v2 - - - name: Check Tag - id: check-tag - shell: bash - run: | - tag=${GITHUB_REF##*/} - echo "::set-output name=version::$tag" - if [[ "$tag" =~ [0-9]+.[0-9]+.[0-9]+$ ]]; then - echo "::set-output name=rc::false" - else - echo "::set-output name=rc::true" - fi - - - - name: Install Rust Toolchain Components - uses: actions-rs/toolchain@v1 - with: - override: true - target: ${{ matrix.target }} - toolchain: stable - profile: minimal # minimal component installation (ie, no documentation) - - - name: Show Version Information (Rust, cargo, GCC) - shell: bash - run: | - gcc --version || true - rustup -V - rustup toolchain list - rustup default - cargo -V - rustc -V - - - name: Build - uses: actions-rs/cargo@v1 - with: - use-cross: ${{ matrix.use-cross }} - command: build - args: --locked --release --target=${{ matrix.target }} ${{ matrix.cargo-flags }} - - - name: Build Archive - shell: bash - id: package - env: - target: ${{ matrix.target }} - version: ${{ steps.check-tag.outputs.version }} - run: | - set -euxo pipefail - - bin=${GITHUB_REPOSITORY##*/} - src=`pwd` - dist=$src/dist - name=$bin-$version-$target - executable=target/$target/release/$bin - - if [[ "$RUNNER_OS" == "Windows" ]]; then - executable=$executable.exe - fi - - mkdir $dist - cp $executable $dist - cd $dist - - if [[ "$RUNNER_OS" == "Windows" ]]; then - archive=$dist/$name.zip - 7z a $archive * - echo "::set-output name=archive::`pwd -W`/$name.zip" - else - archive=$dist/$name.tar.gz - tar czf $archive * - echo "::set-output name=archive::$archive" - fi - - - name: Publish Archive - uses: softprops/action-gh-release@v0.1.5 - if: ${{ startsWith(github.ref, 'refs/tags/') }} - with: - draft: false - files: ${{ steps.package.outputs.archive }} - prerelease: ${{ steps.check-tag.outputs.rc == 'true' }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index a930d6b..64fccb2 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,6 @@ # will have compiled files and executables debug/ target/ -result -.direnv/ # These are backup files generated by rustfmt **/*.rs.bk @@ -11,4 +9,4 @@ result # MSVC Windows builds of rustc generate these, which store debugging information *.pdb -pasta_data/* +pasta_data/* \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index bab4773..220c77b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1159,35 +1159,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "karton" -version = "2.0.1" -dependencies = [ - "actix-files", - "actix-multipart", - "actix-web", - "actix-web-httpauth", - "askama", - "askama-filters", - "bytesize", - "chrono", - "clap", - "env_logger", - "futures", - "harsh", - "lazy_static", - "linkify", - "log", - "mime_guess", - "qrcode-generator", - "rand", - "rust-embed", - "sanitize-filename", - "serde", - "serde_json", - "syntect", -] - [[package]] name = "language-tags" version = "0.3.2" @@ -1310,6 +1281,35 @@ dependencies = [ "autocfg", ] +[[package]] +name = "microbin" +version = "1.2.0" +dependencies = [ + "actix-files", + "actix-multipart", + "actix-web", + "actix-web-httpauth", + "askama", + "askama-filters", + "bytesize", + "chrono", + "clap", + "env_logger", + "futures", + "harsh", + "lazy_static", + "linkify", + "log", + "mime_guess", + "qrcode-generator", + "rand", + "rust-embed", + "sanitize-filename", + "serde", + "serde_json", + "syntect", +] + [[package]] name = "mime" version = "0.3.16" diff --git a/Cargo.toml b/Cargo.toml index 6432d52..54e79a8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,14 +1,14 @@ [package] -name = "karton" -version = "2.0.1" +name = "microbin" +version = "1.2.0" edition = "2021" -authors = ["Jade ", "Daniel Szabo "] +authors = ["Daniel Szabo "] license = "BSD-3-Clause" description = "Simple, performant, configurable, entirely self-contained Pastebin and URL shortener." readme = "README.md" -homepage = "https://gitlab.com/obsidianical/microbin" -repository = "https://gitlab.com/obsidianical/microbin" -keywords = ["pastebin", "karton", "microbin", "actix", "selfhosted"] +homepage = "https://github.com/szabodanika/microbin" +repository = "https://github.com/szabodanika/microbin" +keywords = ["pastebin", "pastabin", "microbin", "actix", "selfhosted"] categories = ["pastebins"] [dependencies] diff --git a/Dockerfile b/Dockerfile index 4fb8ae3..3e10f7a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM docker.io/rust:latest as build +FROM rust:latest as build WORKDIR /app @@ -8,11 +8,10 @@ RUN \ DEBIAN_FRONTEND=noninteractive \ apt-get update &&\ apt-get -y install ca-certificates tzdata &&\ - CARGO_NET_GIT_FETCH_WITH_CLI=true \ cargo build --release # https://hub.docker.com/r/bitnami/minideb -FROM docker.io/bitnami/minideb:latest +FROM bitnami/minideb:latest # microbin will be in /app WORKDIR /app @@ -26,12 +25,12 @@ COPY --from=build \ /etc/ssl/certs/ca-certificates.crt \ /etc/ssl/certs/ca-certificates.crt -# copy built executable +# copy built exacutable COPY --from=build \ - /app/target/release/karton \ - /usr/bin/karton + /app/target/release/microbin \ + /usr/bin/microbin # Expose webport used for the webserver to the docker runtime EXPOSE 8080 -ENTRYPOINT ["karton"] +ENTRYPOINT ["microbin"] \ No newline at end of file diff --git a/README.MD b/README.MD new file mode 100644 index 0000000..474dc03 --- /dev/null +++ b/README.MD @@ -0,0 +1,70 @@ + +![Screenshot](.github/index.png) + +# MicroBin + +![Build](https://github.com/szabodanika/microbin/actions/workflows/rust.yml/badge.svg) +![crates.io](https://img.shields.io/crates/v/microbin.svg) +[![Docker Image](https://github.com/szabodanika/microbin/actions/workflows/docker.yml/badge.svg)](https://hub.docker.com/r/danielszabo99/microbin) + +MicroBin is a super tiny, feature rich, configurable, self-contained and self-hosted paste bin web application. It is very easy to set up and use, and will only require a few megabytes of memory and disk storage. It takes only a couple minutes to set it up, why not give it a try now? + +Install from Cargo: + +`cargo install microbin` + +And run with your custom configuration: + +`microbin --port 8080 --public-path https://myserver.net --highlightsyntax --editable` + +Or get the Docker image from [Dockerhub: danielszabo99/microbin](https://hub.docker.com/r/danielszabo99/microbin) + +On our website [microbin.eu](microbin.eu) you will find the following: + +- [Screenshots](https://microbin.eu/screenshots/) +- [Quickstart Guide](https://microbin.eu/quickstart/) +- [Documentation](https://microbin.eu/documentation/) +- [Donations and Sponsorhip](https://microbin.eu/donate/) +- [Community](https://microbin.eu/community/) + +### Features +- Is very small +- Entirely self-contained executable, MicroBin is a single file! +- Animal names instead of random numbers for pasta identifiers (64 animals) +- File uploads (eg. server.com/file/pig-dog-cat) +- Raw text serving (eg. server.com/raw/pig-dog-cat) +- URL shortening and redirection +- QR code support +- Very simple database (JSON + files) for portability, easy backups and integration +- Listing and manually removing pastas (/pastalist) +- Private and public, editable and final, automatically and never expiring pastas +- Syntax highlighting +- Automatic dark mode and custom styling support with very little CSS and only vanilla JS (see [water.css](https://github.com/kognise/water.css)) +- Most of the above can be toggled on and off! + +### What is a "pasta" anyway? + +In microbin, a pasta can be: +- A text that you want to paste from one machine to another, eg. some code, +- A file that you want to share, eg. a video that is too large for Discord, a zip with a code project in it or an image, +- A URL redirect. + +### When is MicroBin useful? + +You can use MicroBin +- As a URL shortener/redirect service, +- To send long texts to other people, +- To send large files to other people, +- To serve content on the web, eg. configuration files for testing, images, or any other file content using the Raw functionality, +- To move files between your desktop and a server you access from the console, +- As a "postbox" service where people can upload their files or texts, but they cannot see or remove what others sent you - just disable the pastalist page +- To take notes! Simply create an editable pasta. + +...and many other things, why not get creative? + + +### License + +MicroBin and MicroBin.eu are available under the BSD 3-Clause License. + +© Dániel Szabó 2022 \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index b5f6a08..0000000 --- a/README.md +++ /dev/null @@ -1,76 +0,0 @@ -# Karton - -A small, rusty pastebin with URL shortener functionality. - -The github repository is a mirror of [this gitlab repository](https://gitlab.com/obsidianical/microbin). - -This is a fork of [MicroBin](https://github.com/szabodanika/microbin). - -## Features - -- Animal names (by default) or custom namefiles instead of just hashes (though hashes are an option too!) -- File and image uploads -- raw text serving -- URL shortening -- QR codes -- Listing and removing pastas (though currently everyone can do that) -- Expiration times -- Editable pastas -- Syntax highlighting -- Styling via [water.css](https://github.com/kognise/water.css) -- Customizable endpoints - -## Installation guide - -Karton is available on [Docker hub](https://hub.docker.com/r/schrottkatze/karton), [crates.io](https://crates.io/crates/karton) and using the nix flake. - -The only "officially supported" (I will actively debug and search for the problem) method is the last one using nix flakes. - -### Installation via the nix flake - -Add the repository to your inputs. - -```nix - karton.url = "git+https://gitlab.com/obsidianical/microbin.git"; -``` - -```nix -# microbin.nix -{ inputs, config, pkgs, ... }: -{ - environment.systemPackages = [ inputs.karton.defaultPackage."x86_64-linux" ]; - systemd.services.karton = { - after = [ "network.target" ]; - wantedBy = [ "multi-user.target" ]; - environment = { - # set environment variables to configure karton - KARTON_HASH_IDS = ""; - KARTON_EDITABLE = ""; - KARTON_PRIVATE = ""; - KARTON_HIGHLIGHTSYNTAX = ""; - # adjust this to your domain - KARTON_PUBLIC_PATH = "https://example.org"; - KARTON_QR = ""; - # configure endpoints to be shorter - KARTON_URL_EP = "u"; - KARTON_RAW_EP = "r"; - KARTON_PASTA_EP = "p"; - }; - script = "${inputs.karton.defaultPackage."x86_64-linux"}/bin/karton"; - # register a simple systemd service - serviceConfig = { - Type = "simple"; - RootDirectory="/"; - WorkingDirectory = "/karton"; - }; - }; -} -``` - -## Contact - -This fork of MicroBin was created by [Schrottkatze](https://schrottkatze.de). - -Join [the matrix room](https://matrix.to/#/#s10e-microbin:matrix.org) to chat! - -Contact me via e-mail at [contact@schrottkatze.de](mailto:contact@schrottkatze.de). diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..695fc0f --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,12 @@ +# Security Policy + +## Version Support + +Currently we only have capacity to support the latest version of MicroBin. We recommend that you always update to the newest one and check our pages regularly for announcements. + +## Reporting a Vulnerability + +Security vulnerabilities can be reported directly to the developer/maintainer at d@szab.eu. + +Sensitive information may be GPG encrypted with my public key available at +https://szab.eu/assets/files/daniel-szabo-pub.asc. diff --git a/TODO.md b/TODO.md deleted file mode 100644 index 559543e..0000000 --- a/TODO.md +++ /dev/null @@ -1,54 +0,0 @@ -# TODO lists - -these are just rough guides tho - -## v2.1 - -- [ ] customizable endpoints - - [ ] create - - [ ] edit - - [ ] info - - [ ] get pastas - - [ ] remove -- [ ] improve remove endpoint - - [ ] disable it -- [ ] client library - - [ ] request .well-known data - - [ ] support most endpoints -- [ ] karton cli - -## v3.0 - -- [ ] internal rewrite & docs -- [ ] design new frontend -- [ ] switch to yew - - [ ] using client lib -- [ ] theme and general config files - - [ ] unified theme format - - [ ] no env configs anymore if possible -- [ ] proper dbs - - [ ] sqlite - - [ ] postgres -- [ ] apis/endpoints - - [ ] IDs, name IDs AND user/pastaname - - [ ] root (and admin) user for root level pastas -- [ ] status/instance health admin dashboard and ap - - [ ] storage - - [ ] db status - - [ ] how up to date - - [ ] stats (users etc) - - [ ] errors - - [ ] loading speeds, performance monitor? - - [ ] memory use -- [ ] auth - - [ ] general auth - - [ ] oidc - - [ ] permssion system & api keys - - [ ] only allow some other users to open pasta - - [ ] access control and editing - - [ ] password protected pastas too -- [ ] features for pastas - - [ ] pw protection - - [ ] better editor - - [ ] markdown pastas - - [ ] optional, opt-in commenting diff --git a/default.nix b/default.nix deleted file mode 100644 index 39bacff..0000000 --- a/default.nix +++ /dev/null @@ -1,7 +0,0 @@ -(import ( - fetchTarball { - url = "https://github.com/edolstra/flake-compat/archive/99f1c2157fba4bfe6211a321fd0ee43199025dbf.tar.gz"; - sha256 = "0x2jn3vrawwv9xp15674wjz9pixwjyj3j771izayl962zziivbx2"; } -) { - src = ./.; -}).defaultNix diff --git a/flake.lock b/flake.lock deleted file mode 100644 index 9e09da8..0000000 --- a/flake.lock +++ /dev/null @@ -1,77 +0,0 @@ -{ - "nodes": { - "naersk": { - "inputs": { - "nixpkgs": "nixpkgs" - }, - "locked": { - "lastModified": 1671096816, - "narHash": "sha256-ezQCsNgmpUHdZANDCILm3RvtO1xH8uujk/+EqNvzIOg=", - "owner": "nix-community", - "repo": "naersk", - "rev": "d998160d6a076cfe8f9741e56aeec7e267e3e114", - "type": "github" - }, - "original": { - "owner": "nix-community", - "ref": "master", - "repo": "naersk", - "type": "github" - } - }, - "nixpkgs": { - "locked": { - "lastModified": 1677852945, - "narHash": "sha256-liiVJjkBTuBTAkRW3hrI8MbPD2ImYzwUpa7kvteiKhM=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "f5ffd5787786dde3a8bf648c7a1b5f78c4e01abb", - "type": "github" - }, - "original": { - "id": "nixpkgs", - "type": "indirect" - } - }, - "nixpkgs_2": { - "locked": { - "lastModified": 1677852945, - "narHash": "sha256-liiVJjkBTuBTAkRW3hrI8MbPD2ImYzwUpa7kvteiKhM=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "f5ffd5787786dde3a8bf648c7a1b5f78c4e01abb", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixpkgs-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "root": { - "inputs": { - "naersk": "naersk", - "nixpkgs": "nixpkgs_2", - "utils": "utils" - } - }, - "utils": { - "locked": { - "lastModified": 1676283394, - "narHash": "sha256-XX2f9c3iySLCw54rJ/CZs+ZK6IQy7GXNY4nSOyu2QG4=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "3db36a8b464d0c4532ba1c7dda728f4576d6d073", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/flake.nix b/flake.nix deleted file mode 100644 index c5fcff0..0000000 --- a/flake.nix +++ /dev/null @@ -1,21 +0,0 @@ -{ - inputs = { - naersk.url = "github:nix-community/naersk/master"; - nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; - utils.url = "github:numtide/flake-utils"; - }; - - outputs = { self, nixpkgs, utils, naersk }: - utils.lib.eachDefaultSystem (system: - let - pkgs = import nixpkgs { inherit system; }; - naersk-lib = pkgs.callPackage naersk { }; - in - { - defaultPackage = naersk-lib.buildPackage ./.; - devShell = with pkgs; mkShell { - buildInputs = [ cargo rustc rustfmt pre-commit rustPackages.clippy cargo-watch podman ]; - RUST_SRC_PATH = rustPlatform.rustLibSrc; - }; - }); -} diff --git a/render.yaml b/render.yaml new file mode 100644 index 0000000..3c3e430 --- /dev/null +++ b/render.yaml @@ -0,0 +1,9 @@ +services: + - type: web + name: microbin + plan: free + numInstances: 1 + env: rust + repo: https://github.com/szabodanika/microbin.git + buildCommand: cargo build --release + startCommand: ./target/release/microbin --editable --highlightsyntax diff --git a/shell.nix b/shell.nix deleted file mode 100644 index 77db547..0000000 --- a/shell.nix +++ /dev/null @@ -1,7 +0,0 @@ -(import ( - fetchTarball { - url = "https://github.com/edolstra/flake-compat/archive/99f1c2157fba4bfe6211a321fd0ee43199025dbf.tar.gz"; - sha256 = "0x2jn3vrawwv9xp15674wjz9pixwjyj3j771izayl962zziivbx2"; } -) { - src = ./.; -}).shellNix diff --git a/src/args.rs b/src/args.rs index 1920658..1dc3e72 100644 --- a/src/args.rs +++ b/src/args.rs @@ -3,7 +3,6 @@ use lazy_static::lazy_static; use std::convert::Infallible; use std::fmt; use std::net::IpAddr; -use std::path::PathBuf; use std::str::FromStr; lazy_static! { @@ -13,138 +12,86 @@ lazy_static! { #[derive(Parser, Debug, Clone)] #[clap(author, version, about, long_about = None)] pub struct Args { - /// The username for basic HTTP auth. - /// If unset, HTTP authentication stays disabled. - /// - /// WARNING: people opening pastas will have to authenticate too. - #[clap(long, env = "KARTON_AUTH_USERNAME")] + #[clap(long, env = "MICROBIN_AUTH_USERNAME")] pub auth_username: Option, - /// Set a password for HTTP authentication. - /// If unset, HTTP authentication will not require a password. - /// If `auth_username` is unset, this option will not have any effect. - #[clap(long, env = "KARTON_AUTH_PASSWORD")] + #[clap(long, env = "MICROBIN_AUTH_PASSWORD")] pub auth_password: Option, - /// Enable the option to make pastas editable. - #[clap(long, env = "KARTON_EDITABLE")] + #[clap(long, env = "MICROBIN_EDITABLE")] pub editable: bool, - /// The text displayed in the browser navigation bar. - #[clap(long, env = "KARTON_TITLE", default_value = " Karton")] - pub title: String, - - /// The web interfaces' footer text. - #[clap(long, env = "KARTON_FOOTER_TEXT")] + #[clap(long, env = "MICROBIN_FOOTER_TEXT")] pub footer_text: Option, - /// Hide the footer of the web interface. - #[clap(long, env = "KARTON_HIDE_FOOTER")] + #[clap(long, env = "MICROBIN_HIDE_FOOTER")] pub hide_footer: bool, - /// Hide the header of the web interface. - #[clap(long, env = "KARTON_HIDE_HEADER")] + #[clap(long, env = "MICROBIN_HIDE_HEADER")] pub hide_header: bool, - /// Hide the logo in the header. - #[clap(long, env = "KARTON_HIDE_LOGO")] + #[clap(long, env = "MICROBIN_HIDE_LOGO")] pub hide_logo: bool, - /// Disable the listing page. - #[clap(long, env = "KARTON_NO_LISTING")] + #[clap(long, env = "MICROBIN_NO_LISTING")] pub no_listing: bool, - /// Enable syntax highlighting in pastas. - #[clap(long, env = "KARTON_HIGHLIGHTSYNTAX")] + #[clap(long, env = "MICROBIN_HIGHLIGHTSYNTAX")] pub highlightsyntax: bool, - /// The port to which to bind the server. - #[clap(short, long, env = "KARTON_PORT", default_value_t = 8080)] + #[clap(short, long, env = "MICROBIN_PORT", default_value_t = 8080)] pub port: u16, - /// The IP adress to bind the server to. - #[clap(short, long, env="KARTON_BIND", default_value_t = IpAddr::from([0, 0, 0, 0]))] + #[clap(short, long, env="MICROBIN_BIND", default_value_t = IpAddr::from([0, 0, 0, 0]))] pub bind: IpAddr, - /// Enable the option to create private pastas. - #[clap(long, env = "KARTON_PRIVATE")] + #[clap(long, env = "MICROBIN_PRIVATE")] pub private: bool, - /// Disables most css, apart form some inline styles. - #[clap(long, env = "KARTON_PURE_HTML")] + #[clap(long, env = "MICROBIN_PURE_HTML")] pub pure_html: bool, - /// The servers public path, making it possible to run Karton behind a reverse proxy subpath. - #[clap(long, env="KARTON_PUBLIC_PATH", default_value_t = PublicUrl(String::from("")))] + #[clap(long, env="MICROBIN_PUBLIC_PATH", default_value_t = PublicUrl(String::from("")))] pub public_path: PublicUrl, - /// Enable creation of QR codes of pastas. Requires `public_path` to be set. - #[clap(long, env = "KARTON_QR")] - pub qr: bool, - - - /// Disable adding/removing/editing pastas. - #[clap(long, env = "KARTON_READONLY")] + #[clap(long, env = "MICROBIN_READONLY")] pub readonly: bool, - /// The amount of worker threads that the server is allowed to have. - #[clap(short, long, env = "KARTON_THREADS", default_value_t = 1)] + #[clap(long, env = "MICROBIN_TITLE")] + pub title: Option, + + #[clap(short, long, env = "MICROBIN_THREADS", default_value_t = 1)] pub threads: u8, - /// Sets a time value for the garbage collector. Pastas that aren't accessed for the given - /// amount of days will be deleted. Set to 0 to disable garbage collection. - #[clap(short, long, env = "KARTON_GC_DAYS", default_value_t = 90)] + #[clap(short, long, env = "MICROBIN_GC_DAYS", default_value_t = 90)] pub gc_days: u16, - /// Enable the option to delete after a given amount of reads. - #[clap(long, env = "KARTON_ENABLE_BURN_AFTER")] + #[clap(long, env = "MICROBIN_ENABLE_BURN_AFTER")] pub enable_burn_after: bool, - /// The default amount of reads for the self-delete mechanism. - #[clap(short, long, env = "KARTON_DEFAULT_BURN_AFTER", default_value_t = 0)] + #[clap(short, long, env = "MICROBIN_DEFAULT_BURN_AFTER", default_value_t = 0)] pub default_burn_after: u16, - /// Changes the UIs maximum width from 720 pixels to 1080. - #[clap(long, env = "KARTON_WIDE")] + #[clap(long, env = "MICROBIN_WIDE")] pub wide: bool, - /// Disable "Never" expiry setting. - #[clap(long, env = "KARTON_NO_ETERNAL_PASTA")] + #[clap(long, env = "MICROBIN_QR")] + pub qr: bool, + + #[clap(long, env = "MICROBIN_NO_ETERNAL_PASTA")] pub no_eternal_pasta: bool, - /// Set the default expiry time value. - #[clap(long, env = "KARTON_DEFAULT_EXPIRY", default_value = "24hour")] + #[clap(long, env = "MICROBIN_DEFAULT_EXPIRY", default_value = "24hour")] pub default_expiry: String, - /// Disable file uploading. - #[clap(short, long, env = "KARTON_NO_FILE_UPLOAD")] + #[clap(short, long, env = "MICROBIN_NO_FILE_UPLOAD")] pub no_file_upload: bool, - // TODO: replace with simple path. - /// Replace built-in CSS file with a CSS file provided by the linked URL. - #[clap(long, env = "KARTON_CUSTOM_CSS")] + #[clap(long, env = "MICROBIN_CUSTOM_CSS")] pub custom_css: Option, - /// Replace built-in animal names file with custom names file for pasta links. - /// The file must be newline seperated. - #[clap(long, env = "KARTON_CUSTOM_NAMES")] - pub custom_names: Option, - - /// Enable the use of Hash IDs for shorter URLs instead of animal names. - #[clap(long, env = "KARTON_HASH_IDS")] + #[clap(long, env = "MICROBIN_HASH_IDS")] pub hash_ids: bool, - - /// Endpoint for /url/ - #[clap(long, env = "KARTON_URL_EP", default_value = "url" )] - pub url_endpoint: String, - - /// Endpoint for /pasta/ - #[clap(long, env = "KARTON_PASTA_EP", default_value = "pasta" )] - pub pasta_endpoint: String, - - /// Endpoint for /raw/ - #[clap(long, env = "KARTON_RAW_EP", default_value = "raw" )] - pub raw_endpoint: String, } #[derive(Debug, Clone)] diff --git a/src/endpoints/create.rs b/src/endpoints/create.rs index 917e959..715007e 100644 --- a/src/endpoints/create.rs +++ b/src/endpoints/create.rs @@ -1,12 +1,10 @@ use crate::dbio::save_to_file; use crate::pasta::PastaFile; +use crate::util::animalnumbers::to_animal_names; use crate::util::hashids::to_hashids; use crate::util::misc::is_valid_url; -use crate::util::pasta_id_converter::CONVERTER; use crate::{AppState, Pasta, ARGS}; use actix_multipart::Multipart; -use actix_web::http::StatusCode; -use actix_web::web::{BytesMut, BufMut}; use actix_web::{get, web, Error, HttpResponse, Responder}; use askama::Template; use bytesize::ByteSize; @@ -16,8 +14,6 @@ use rand::Rng; use std::io::Write; use std::time::{SystemTime, UNIX_EPOCH}; -use super::errors::ErrorTemplate; - #[derive(Template)] #[template(path = "index.html")] struct IndexTemplate<'a> { @@ -31,7 +27,6 @@ pub async fn index() -> impl Responder { .body(IndexTemplate { args: &ARGS }.render().unwrap()) } -/// Pasta creation endpoint. pub async fn create( data: web::Data, mut payload: Multipart, @@ -42,7 +37,7 @@ pub async fn create( .finish()); } - let mut pastas = data.pastas.lock().await; + let mut pastas = data.pastas.lock().unwrap(); let timenow: i64 = match SystemTime::now().duration_since(UNIX_EPOCH) { Ok(n) => n.as_secs(), @@ -70,20 +65,22 @@ pub async fn create( while let Some(mut field) = payload.try_next().await? { match field.name() { "editable" => { + // while let Some(_chunk) = field.try_next().await? {} new_pasta.editable = true; + continue; } "private" => { + // while let Some(_chunk) = field.try_next().await? {} new_pasta.private = true; + continue; } "expiration" => { while let Some(chunk) = field.try_next().await? { new_pasta.expiration = match std::str::from_utf8(&chunk).unwrap() { - // TODO: customizable times "1min" => timenow + 60, "10min" => timenow + 60 * 10, "1hour" => timenow + 60 * 60, "24hour" => timenow + 60 * 60 * 24, - "3days" => timenow + 60 * 60 * 24 * 3, "1week" => timenow + 60 * 60 * 24 * 7, "never" => { if ARGS.no_eternal_pasta { @@ -98,12 +95,12 @@ pub async fn create( } }; } + + continue; } "burn_after" => { while let Some(chunk) = field.try_next().await? { new_pasta.burn_after_reads = match std::str::from_utf8(&chunk).unwrap() { - // TODO: also make customizable - // maybe options in config files, with defaults // give an extra read because the user will be redirected to the pasta page automatically "1" => 2, "10" => 10, @@ -117,22 +114,16 @@ pub async fn create( } }; } + + continue; } "content" => { - let mut content = BytesMut::new(); + let mut content = String::from(""); while let Some(chunk) = field.try_next().await? { - content.put(chunk); + content.push_str(std::str::from_utf8(&chunk).unwrap().to_string().as_str()); } - if !content.is_empty() { - new_pasta.content = match String::from_utf8(content.to_vec()) { - Ok(v) => v, - Err(_) => return Ok(HttpResponse::BadRequest() - .content_type("text/html") - .body(ErrorTemplate { - status_code: StatusCode::BAD_REQUEST, - args: &ARGS - }.render().unwrap())), - }; + if content.len() > 0 { + new_pasta.content = content; new_pasta.pasta_type = if is_valid_url(new_pasta.content.as_str()) { String::from("url") @@ -140,11 +131,13 @@ pub async fn create( String::from("text") }; } + continue; } "syntax-highlight" => { while let Some(chunk) = field.try_next().await? { new_pasta.extension = std::str::from_utf8(&chunk).unwrap().to_string(); } + continue; } "file" => { if ARGS.no_file_upload { @@ -159,7 +152,7 @@ pub async fn create( None => continue, }; - let mut file = match PastaFile::from_unsanitized(path) { + let mut file = match PastaFile::from_unsanitized(&path) { Ok(f) => f, Err(e) => { warn!("Unsafe file name: {e:?}"); @@ -206,9 +199,9 @@ pub async fn create( let slug = if ARGS.hash_ids { to_hashids(id) } else { - CONVERTER.to_names(id) + to_animal_names(id) }; Ok(HttpResponse::Found() - .append_header(("Location", format!("{}/{}/{}", ARGS.public_path, ARGS.pasta_endpoint, slug))) + .append_header(("Location", format!("{}/pasta/{}", ARGS.public_path, slug))) .finish()) } diff --git a/src/endpoints/edit.rs b/src/endpoints/edit.rs index bfa4dba..b361aec 100644 --- a/src/endpoints/edit.rs +++ b/src/endpoints/edit.rs @@ -1,12 +1,11 @@ use crate::args::Args; use crate::dbio::save_to_file; use crate::endpoints::errors::ErrorTemplate; +use crate::util::animalnumbers::to_u64; use crate::util::hashids::to_u64 as hashid_to_u64; use crate::util::misc::remove_expired; -use crate::util::pasta_id_converter::CONVERTER; use crate::{AppState, Pasta, ARGS}; use actix_multipart::Multipart; -use actix_web::http::StatusCode; use actix_web::{get, post, web, Error, HttpResponse}; use askama::Template; use futures::TryStreamExt; @@ -20,12 +19,12 @@ struct EditTemplate<'a> { #[get("/edit/{id}")] pub async fn get_edit(data: web::Data, id: web::Path) -> HttpResponse { - let mut pastas = data.pastas.lock().await; + let mut pastas = data.pastas.lock().unwrap(); let id = if ARGS.hash_ids { - hashid_to_u64(&id).unwrap_or(0) + hashid_to_u64(&*id).unwrap_or(0) } else { - CONVERTER.to_u64(&id.into_inner()).unwrap_or(0) + to_u64(&*id.into_inner()).unwrap_or(0) }; remove_expired(&mut pastas); @@ -37,18 +36,20 @@ pub async fn get_edit(data: web::Data, id: web::Path) -> HttpR .append_header(("Location", format!("{}/", ARGS.public_path))) .finish(); } - return HttpResponse::Ok() - .content_type("text/html") - .body(EditTemplate { pasta, args: &ARGS }.render().unwrap()); + return HttpResponse::Ok().content_type("text/html").body( + EditTemplate { + pasta: &pasta, + args: &ARGS, + } + .render() + .unwrap(), + ); } } - HttpResponse::NotFound() + HttpResponse::Ok() .content_type("text/html") - .body(ErrorTemplate { - status_code: StatusCode::NOT_FOUND, - args: &ARGS - }.render().unwrap()) + .body(ErrorTemplate { args: &ARGS }.render().unwrap()) } #[post("/edit/{id}")] @@ -64,29 +65,32 @@ pub async fn post_edit( } let id = if ARGS.hash_ids { - hashid_to_u64(&id).unwrap_or(0) + hashid_to_u64(&*id).unwrap_or(0) } else { - CONVERTER.to_u64(&id.into_inner()).unwrap_or(0) + to_u64(&*id.into_inner()).unwrap_or(0) }; - let mut pastas = data.pastas.lock().await; + let mut pastas = data.pastas.lock().unwrap(); remove_expired(&mut pastas); let mut new_content = String::from(""); while let Some(mut field) = payload.try_next().await? { - if field.name() == "content" { - while let Some(chunk) = field.try_next().await? { - new_content = std::str::from_utf8(&chunk).unwrap().to_string(); + match field.name() { + "content" => { + while let Some(chunk) = field.try_next().await? { + new_content = std::str::from_utf8(&chunk).unwrap().to_string(); + } } + _ => {} } } for (i, pasta) in pastas.iter().enumerate() { if pasta.id == id { if pasta.editable { - pastas[i].content.replace_range(.., &new_content); + pastas[i].content.replace_range(.., &*new_content); save_to_file(&pastas); return Ok(HttpResponse::Found() @@ -101,10 +105,7 @@ pub async fn post_edit( } } - Ok(HttpResponse::NotFound() + Ok(HttpResponse::Ok() .content_type("text/html") - .body(ErrorTemplate { - status_code: StatusCode::NOT_FOUND, - args: &ARGS - }.render().unwrap())) + .body(ErrorTemplate { args: &ARGS }.render().unwrap())) } diff --git a/src/endpoints/errors.rs b/src/endpoints/errors.rs index 744ede5..04626b4 100644 --- a/src/endpoints/errors.rs +++ b/src/endpoints/errors.rs @@ -1,4 +1,4 @@ -use actix_web::{Error, HttpResponse, http::StatusCode}; +use actix_web::{Error, HttpResponse}; use askama::Template; use crate::args::{Args, ARGS}; @@ -6,15 +6,11 @@ use crate::args::{Args, ARGS}; #[derive(Template)] #[template(path = "error.html")] pub struct ErrorTemplate<'a> { - pub status_code: StatusCode, pub args: &'a Args, } pub async fn not_found() -> Result { - Ok(HttpResponse::NotFound() + Ok(HttpResponse::Ok() .content_type("text/html") - .body(ErrorTemplate { - status_code: StatusCode::NOT_FOUND, - args: &ARGS - }.render().unwrap())) + .body(ErrorTemplate { args: &ARGS }.render().unwrap())) } diff --git a/src/endpoints/info.rs b/src/endpoints/info.rs index 173ac05..b2d8cd2 100644 --- a/src/endpoints/info.rs +++ b/src/endpoints/info.rs @@ -9,22 +9,17 @@ use askama::Template; struct Info<'a> { args: &'a Args, pastas: &'a Vec, - status: &'a str, - version_string: &'a str, - message: &'a str, + status: &'a String, + version_string: &'a String, + message: &'a String, } -/// Endpoint to get information about the instance. #[get("/info")] pub async fn info(data: web::Data) -> HttpResponse { // get access to the pasta collection - let pastas = data.pastas.lock().await; + let mut pastas = data.pastas.lock().unwrap(); - // TODO: status report more sophisticated - // maybe: - // - detect weird/invalid configurations? - // - detect server storage issues - // - detect performance problems? + // todo status report more sophisticated let mut status = "OK"; let mut message = ""; @@ -37,9 +32,9 @@ pub async fn info(data: web::Data) -> HttpResponse { Info { args: &ARGS, pastas: &pastas, - status, - version_string: env!("CARGO_PKG_VERSION"), - message + status: &String::from(status), + version_string: &String::from("1.2.0-20221107"), + message: &String::from(message), } .render() .unwrap(), diff --git a/src/endpoints/pasta.rs b/src/endpoints/pasta.rs index 39ba5a6..208664c 100644 --- a/src/endpoints/pasta.rs +++ b/src/endpoints/pasta.rs @@ -2,13 +2,12 @@ use crate::args::{Args, ARGS}; use crate::dbio::save_to_file; use crate::endpoints::errors::ErrorTemplate; use crate::pasta::Pasta; +use crate::util::animalnumbers::to_u64; use crate::util::hashids::to_u64 as hashid_to_u64; use crate::util::misc::remove_expired; use crate::AppState; -use crate::util::pasta_id_converter::CONVERTER; - -use actix_web::http::StatusCode; -use actix_web::{web, HttpResponse}; +use actix_web::rt::time; +use actix_web::{get, web, HttpResponse}; use askama::Template; use std::time::{SystemTime, UNIX_EPOCH}; @@ -19,15 +18,15 @@ struct PastaTemplate<'a> { args: &'a Args, } -/// Endpoint to view a pasta. +#[get("/pasta/{id}")] pub async fn getpasta(data: web::Data, id: web::Path) -> HttpResponse { // get access to the pasta collection - let mut pastas = data.pastas.lock().await; + let mut pastas = data.pastas.lock().unwrap(); let id = if ARGS.hash_ids { - hashid_to_u64(&id).unwrap_or(0) + hashid_to_u64(&*id).unwrap_or(0) } else { - CONVERTER.to_u64(&id.into_inner()).unwrap_or(0) + to_u64(&*id.into_inner()).unwrap_or(0) }; // remove expired pastas (including this one if needed) @@ -46,7 +45,7 @@ pub async fn getpasta(data: web::Data, id: web::Path) -> HttpR if found { // increment read count - pastas[index].read_count += 1; + pastas[index].read_count = pastas[index].read_count + 1; // save the updated read count save_to_file(&pastas); @@ -78,23 +77,20 @@ pub async fn getpasta(data: web::Data, id: web::Path) -> HttpR // otherwise // send pasta not found error - HttpResponse::NotFound() + HttpResponse::Ok() .content_type("text/html") - .body(ErrorTemplate { - status_code: StatusCode::NOT_FOUND, - args: &ARGS - }.render().unwrap()) + .body(ErrorTemplate { args: &ARGS }.render().unwrap()) } -/// Endpoint for redirection. +#[get("/url/{id}")] pub async fn redirecturl(data: web::Data, id: web::Path) -> HttpResponse { // get access to the pasta collection - let mut pastas = data.pastas.lock().await; + let mut pastas = data.pastas.lock().unwrap(); let id = if ARGS.hash_ids { - hashid_to_u64(&id).unwrap_or(0) + hashid_to_u64(&*id).unwrap_or(0) } else { - CONVERTER.to_u64(&id.into_inner()).unwrap_or(0) + to_u64(&*id.into_inner()).unwrap_or(0) }; // remove expired pastas (including this one if needed) @@ -114,7 +110,7 @@ pub async fn redirecturl(data: web::Data, id: web::Path) -> Ht if found { // increment read count - pastas[index].read_count += 1; + pastas[index].read_count = pastas[index].read_count + 1; // save the updated read count save_to_file(&pastas); @@ -140,34 +136,28 @@ pub async fn redirecturl(data: web::Data, id: web::Path) -> Ht return response; // send error if we're trying to open a non-url pasta as a redirect } else { - HttpResponse::NotFound() + HttpResponse::Ok() .content_type("text/html") - .body(ErrorTemplate { - status_code: StatusCode::NOT_FOUND, - args: &ARGS - }.render().unwrap()); + .body(ErrorTemplate { args: &ARGS }.render().unwrap()); } } // otherwise // send pasta not found error - HttpResponse::NotFound() + HttpResponse::Ok() .content_type("text/html") - .body(ErrorTemplate { - status_code: StatusCode::NOT_FOUND, - args: &ARGS - }.render().unwrap()) + .body(ErrorTemplate { args: &ARGS }.render().unwrap()) } -/// Endpoint to request pasta as raw file. +#[get("/raw/{id}")] pub async fn getrawpasta(data: web::Data, id: web::Path) -> String { // get access to the pasta collection - let mut pastas = data.pastas.lock().await; + let mut pastas = data.pastas.lock().unwrap(); let id = if ARGS.hash_ids { - hashid_to_u64(&id).unwrap_or(0) + hashid_to_u64(&*id).unwrap_or(0) } else { - CONVERTER.to_u64(&id.into_inner()).unwrap_or(0) + to_u64(&*id.into_inner()).unwrap_or(0) }; // remove expired pastas (including this one if needed) @@ -186,7 +176,7 @@ pub async fn getrawpasta(data: web::Data, id: web::Path) -> St if found { // increment read count - pastas[index].read_count += 1; + pastas[index].read_count = pastas[index].read_count + 1; // get current unix time in seconds let timenow: i64 = match SystemTime::now().duration_since(UNIX_EPOCH) { diff --git a/src/endpoints/pastalist.rs b/src/endpoints/pastalist.rs index 0ff15e4..e2536e5 100644 --- a/src/endpoints/pastalist.rs +++ b/src/endpoints/pastalist.rs @@ -13,7 +13,6 @@ struct PastaListTemplate<'a> { args: &'a Args, } -/// The endpoint to view all currently registered pastas. #[get("/pastalist")] pub async fn list(data: web::Data) -> HttpResponse { if ARGS.no_listing { @@ -22,7 +21,7 @@ pub async fn list(data: web::Data) -> HttpResponse { .finish(); } - let mut pastas = data.pastas.lock().await; + let mut pastas = data.pastas.lock().unwrap(); remove_expired(&mut pastas); diff --git a/src/endpoints/qr.rs b/src/endpoints/qr.rs index 73dcb9b..ab4ea32 100644 --- a/src/endpoints/qr.rs +++ b/src/endpoints/qr.rs @@ -1,13 +1,14 @@ use crate::args::{Args, ARGS}; use crate::endpoints::errors::ErrorTemplate; use crate::pasta::Pasta; +use crate::util::animalnumbers::to_u64; use crate::util::hashids::to_u64 as hashid_to_u64; use crate::util::misc::{self, remove_expired}; use crate::AppState; -use crate::util::pasta_id_converter::CONVERTER; -use actix_web::http::StatusCode; use actix_web::{get, web, HttpResponse}; use askama::Template; +use qrcode_generator::QrCodeEcc; +use std::time::{SystemTime, UNIX_EPOCH}; #[derive(Template)] #[template(path = "qr.html", escape = "none")] @@ -17,16 +18,15 @@ struct QRTemplate<'a> { args: &'a Args, } -/// Endpoint to open a QR code to a pasta. #[get("/qr/{id}")] pub async fn getqr(data: web::Data, id: web::Path) -> HttpResponse { // get access to the pasta collection - let mut pastas = data.pastas.lock().await; + let mut pastas = data.pastas.lock().unwrap(); let u64_id = if ARGS.hash_ids { hashid_to_u64(&id).unwrap_or(0) } else { - CONVERTER.to_u64(&id).unwrap_or(0) + to_u64(&id).unwrap_or(0) }; // remove expired pastas (including this one if needed) @@ -64,10 +64,7 @@ pub async fn getqr(data: web::Data, id: web::Path) -> HttpResp // otherwise // send pasta not found error - HttpResponse::NotFound() + HttpResponse::Ok() .content_type("text/html") - .body(ErrorTemplate { - status_code: StatusCode::NOT_FOUND, - args: &ARGS - }.render().unwrap()) + .body(ErrorTemplate { args: &ARGS }.render().unwrap()) } diff --git a/src/endpoints/remove.rs b/src/endpoints/remove.rs index 1dbe997..0378a00 100644 --- a/src/endpoints/remove.rs +++ b/src/endpoints/remove.rs @@ -1,17 +1,15 @@ -use actix_web::http::StatusCode; use actix_web::{get, web, HttpResponse}; use crate::args::ARGS; use crate::endpoints::errors::ErrorTemplate; use crate::pasta::PastaFile; +use crate::util::animalnumbers::to_u64; use crate::util::hashids::to_u64 as hashid_to_u64; use crate::util::misc::remove_expired; use crate::AppState; -use crate::util::pasta_id_converter::CONVERTER; use askama::Template; use std::fs; -/// Endpoint to remove a pasta. #[get("/remove/{id}")] pub async fn remove(data: web::Data, id: web::Path) -> HttpResponse { if ARGS.readonly { @@ -20,12 +18,12 @@ pub async fn remove(data: web::Data, id: web::Path) -> HttpRes .finish(); } - let mut pastas = data.pastas.lock().await; + let mut pastas = data.pastas.lock().unwrap(); let id = if ARGS.hash_ids { - hashid_to_u64(&id).unwrap_or(0) + hashid_to_u64(&*id).unwrap_or(0) } else { - CONVERTER.to_u64(&id.into_inner()).unwrap_or(0) + to_u64(&*id.into_inner()).unwrap_or(0) }; for (i, pasta) in pastas.iter().enumerate() { @@ -59,10 +57,7 @@ pub async fn remove(data: web::Data, id: web::Path) -> HttpRes remove_expired(&mut pastas); - HttpResponse::NotFound() + HttpResponse::Ok() .content_type("text/html") - .body(ErrorTemplate { - status_code: StatusCode::NOT_FOUND, - args: &ARGS - }.render().unwrap()) + .body(ErrorTemplate { args: &ARGS }.render().unwrap()) } diff --git a/src/endpoints/static_resources.rs b/src/endpoints/static_resources.rs index bb738f4..d314416 100644 --- a/src/endpoints/static_resources.rs +++ b/src/endpoints/static_resources.rs @@ -1,4 +1,4 @@ -use actix_web::{web, HttpResponse, Responder}; +use actix_web::{web, App, HttpResponse, HttpServer, Responder}; use mime_guess::from_path; use rust_embed::RustEmbed; @@ -19,3 +19,45 @@ fn handle_embedded_file(path: &str) -> HttpResponse { async fn static_resources(path: web::Path) -> impl Responder { handle_embedded_file(path.as_str()) } + +// #[derive(Template)] +// #[template(path = "water.css", escape = "none")] +// struct WaterCSS<'a> { +// _marker: PhantomData<&'a ()>, +// } + +// // #[derive(Template)] +// // #[template(path = "logo.png", escape = "none")] +// struct LogoPNG<'a> { +// _marker: PhantomData<&'a ()>, +// } + +// #[derive(Template)] +// #[template(path = "favicon.svg", escape = "none")] +// struct Favicon<'a> { +// _marker: PhantomData<&'a ()>, +// } + +// #[get("/static/{resource}")] +// pub async fn static_resources(resource_id: web::Path) -> HttpResponse { +// match resource_id.into_inner().as_str() { +// "water.css" => HttpResponse::Ok().content_type("text/css").body( +// WaterCSS { +// _marker: Default::default(), +// } +// .render() +// .unwrap(), +// ), +// "logo.png" => HttpResponse::Ok() +// .content_type("image/png") +// .body(Ok(EmbedFile::open("templates/logo.png")?)), +// "favicon.svg" => HttpResponse::Ok().content_type("image/svg+xml").body( +// Favicon { +// _marker: Default::default(), +// } +// .render() +// .unwrap(), +// ), +// _ => HttpResponse::NotFound().content_type("text/html").finish(), +// } +// } diff --git a/src/main.rs b/src/main.rs index fd4f412..cfbcc72 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,16 +11,16 @@ use actix_web::{middleware, web, App, HttpServer}; use actix_web_httpauth::middleware::HttpAuthentication; use chrono::Local; use env_logger::Builder; -use futures::lock::Mutex; use log::LevelFilter; use std::fs; use std::io::Write; +use std::sync::Mutex; pub mod args; pub mod pasta; pub mod util { - pub mod pasta_id_converter; + pub mod animalnumbers; pub mod auth; pub mod dbio; pub mod hashids; @@ -68,8 +68,14 @@ async fn main() -> std::io::Result<()> { match fs::create_dir_all("./pasta_data/public") { Ok(dir) => dir, Err(error) => { - log::error!("Couldn't create data directory ./pasta_data/public/: {error:?}"); - panic!("Couldn't create data directory ./pasta_data/public/: {error:?}"); + log::error!( + "Couldn't create data directory ./pasta_data/public/: {:?}", + error + ); + panic!( + "Couldn't create data directory ./pasta_data/public/: {:?}", + error + ); } }; @@ -83,21 +89,9 @@ async fn main() -> std::io::Result<()> { .wrap(middleware::NormalizePath::trim()) .service(create::index) .service(info::info) - .route( - &format!("/{}/{{id}}", ARGS.pasta_endpoint), - web::get().to(pasta_endpoint::getpasta) - ) - .route( - &format!("/{}/{{id}}", ARGS.raw_endpoint), - web::get().to(pasta_endpoint::getrawpasta) - ) - .route( - &format!("/{}/{{id}}", ARGS.url_endpoint), - web::get().to(pasta_endpoint::redirecturl) - ) - //.service(pasta_endpoint::getpasta) - //.service(pasta_endpoint::getrawpasta) - //.service(pasta_endpoint::redirecturl) + .service(pasta_endpoint::getpasta) + .service(pasta_endpoint::getrawpasta) + .service(pasta_endpoint::redirecturl) .service(edit::get_edit) .service(edit::post_edit) .service(static_resources::static_resources) diff --git a/src/pasta.rs b/src/pasta.rs index 38a215a..f20ce7d 100644 --- a/src/pasta.rs +++ b/src/pasta.rs @@ -6,8 +6,8 @@ use std::path::Path; use std::time::{SystemTime, UNIX_EPOCH}; use crate::args::ARGS; +use crate::util::animalnumbers::to_animal_names; use crate::util::hashids::to_hashids; -use crate::util::pasta_id_converter::CONVERTER; use crate::util::syntaxhighlighter::html_highlight; #[derive(Serialize, Deserialize, PartialEq, Eq)] @@ -30,12 +30,6 @@ impl PastaFile { pub fn name(&self) -> &str { &self.name } - - /// Check if file is an image for embedding - pub fn is_image(&self) -> bool { - let guess = mime_guess::from_path(&self.name).first_or_text_plain(); - guess.type_() == "image" - } } #[derive(Serialize, Deserialize)] @@ -51,8 +45,6 @@ pub struct Pasta { pub last_read: i64, pub read_count: u64, pub burn_after_reads: u64, - // what types can there be? - // `url`, `text`, pub pasta_type: String, } @@ -61,7 +53,7 @@ impl Pasta { if ARGS.hash_ids { to_hashids(self.id) } else { - CONVERTER.to_names(self.id) + to_animal_names(self.id) } } @@ -104,29 +96,29 @@ impl Pasta { // get seconds since last read and convert it to days let days = ((timenow - self.last_read) / 86400) as u16; if days > 1 { - return format!("{days} days ago"); + return format!("{} days ago", days); }; // it's less than 1 day, let's do hours then let hours = ((timenow - self.last_read) / 3600) as u16; if hours > 1 { - return format!("{hours} hours ago"); + return format!("{} hours ago", hours); }; // it's less than 1 hour, let's do minutes then let minutes = ((timenow - self.last_read) / 60) as u16; if minutes > 1 { - return format!("{minutes} minutes ago"); + return format!("{} minutes ago", minutes); }; // it's less than 1 minute, let's do seconds then let seconds = (timenow - self.last_read) as u16; if seconds > 1 { - return format!("{seconds} seconds ago"); + return format!("{} seconds ago", seconds); }; // it's less than 1 second????? - String::from("just now") + return String::from("just now"); } pub fn last_read_days_ago(&self) -> u16 { @@ -140,7 +132,7 @@ impl Pasta { } as i64; // get seconds since last read and convert it to days - ((timenow - self.last_read) / 86400) as u16 + return ((timenow - self.last_read) / 86400) as u16; } pub fn content_syntax_highlighted(&self) -> String { @@ -152,10 +144,7 @@ impl Pasta { } pub fn content_escaped(&self) -> String { - self.content - .replace('`', "\\`") - .replace('$', "\\$") - .replace('/', "\\/") + self.content.replace("`", "\\`").replace("$", "\\$") } } diff --git a/src/util/animalnumbers.rs b/src/util/animalnumbers.rs new file mode 100644 index 0000000..29e5094 --- /dev/null +++ b/src/util/animalnumbers.rs @@ -0,0 +1,53 @@ +const ANIMAL_NAMES: &[&str] = &[ + "ant", "eel", "mole", "sloth", "ape", "emu", "monkey", "snail", "bat", "falcon", "mouse", + "snake", "bear", "fish", "otter", "spider", "bee", "fly", "parrot", "squid", "bird", "fox", + "panda", "swan", "bison", "frog", "pig", "tiger", "camel", "gecko", "pigeon", "toad", "cat", + "goat", "pony", "turkey", "cobra", "goose", "pug", "turtle", "crow", "hawk", "rabbit", "viper", + "deer", "horse", "rat", "wasp", "dog", "jaguar", "raven", "whale", "dove", "koala", "seal", + "wolf", "duck", "lion", "shark", "worm", "eagle", "lizard", "sheep", "zebra", +]; + +pub fn to_animal_names(mut number: u64) -> String { + let mut result: Vec<&str> = Vec::new(); + + if number == 0 { + return ANIMAL_NAMES[0].parse().unwrap(); + } + + let mut power = 6; + + loop { + let digit = number / ANIMAL_NAMES.len().pow(power) as u64; + if !(result.is_empty() && digit == 0) { + result.push(ANIMAL_NAMES[digit as usize]); + } + number -= digit * ANIMAL_NAMES.len().pow(power) as u64; + if power > 0 { + power -= 1; + } else if power <= 0 || number == 0 { + break; + } + } + + result.join("-") +} + +pub fn to_u64(animal_names: &str) -> Result { + let mut result: u64 = 0; + + let animals: Vec<&str> = animal_names.split("-").collect(); + + let mut pow = animals.len(); + for i in 0..animals.len() { + pow -= 1; + let animal_index = ANIMAL_NAMES.iter().position(|&r| r == animals[i]); + match animal_index { + None => return Err("Failed to convert animal name to u64!"), + Some(_) => { + result += (animal_index.unwrap() * ANIMAL_NAMES.len().pow(pow as u32)) as u64 + } + } + } + + Ok(result) +} diff --git a/src/util/dbio.rs b/src/util/dbio.rs index 7c6a5e8..12dff06 100644 --- a/src/util/dbio.rs +++ b/src/util/dbio.rs @@ -4,7 +4,7 @@ use std::io::{BufReader, BufWriter}; use crate::Pasta; -static DATABASE_PATH: &str = "pasta_data/database.json"; +static DATABASE_PATH: &'static str = "pasta_data/database.json"; pub fn save_to_file(pasta_data: &Vec) { let mut file = File::create(DATABASE_PATH); @@ -14,11 +14,11 @@ pub fn save_to_file(pasta_data: &Vec) { serde_json::to_writer(writer, &pasta_data).expect("Failed to create JSON writer"); } Err(_) => { - log::info!("Database file {DATABASE_PATH} not found!"); + log::info!("Database file {} not found!", DATABASE_PATH); file = File::create(DATABASE_PATH); match file { Ok(_) => { - log::info!("Database file {DATABASE_PATH} created."); + log::info!("Database file {} created.", DATABASE_PATH); save_to_file(pasta_data); } Err(err) => { @@ -27,7 +27,7 @@ pub fn save_to_file(pasta_data: &Vec) { &DATABASE_PATH, &err ); - panic!("Failed to create database file {DATABASE_PATH}: {err}!") + panic!("Failed to create database file {}: {}!", DATABASE_PATH, err) } } } @@ -46,10 +46,10 @@ pub fn load_from_file() -> io::Result> { Ok(data) } Err(_) => { - log::info!("Database file {DATABASE_PATH} not found!"); + log::info!("Database file {} not found!", DATABASE_PATH); save_to_file(&Vec::::new()); - log::info!("Database file {DATABASE_PATH} created."); + log::info!("Database file {} created.", DATABASE_PATH); load_from_file() } } diff --git a/src/util/hashids.rs b/src/util/hashids.rs index 0e53f31..e9cdb1f 100644 --- a/src/util/hashids.rs +++ b/src/util/hashids.rs @@ -13,6 +13,6 @@ pub fn to_u64(hash_id: &str) -> Result { let ids = HARSH .decode(hash_id) .map_err(|_e| "Failed to decode hash ID")?; - let id = ids.first().ok_or("No ID found in hash ID")?; + let id = ids.get(0).ok_or("No ID found in hash ID")?; Ok(*id) } diff --git a/src/util/pasta_id_converter.rs b/src/util/pasta_id_converter.rs deleted file mode 100644 index 3d45d68..0000000 --- a/src/util/pasta_id_converter.rs +++ /dev/null @@ -1,98 +0,0 @@ -use std::fs; - -use lazy_static::lazy_static; - -use crate::args::ARGS; - -const ANIMAL_NAMES: &[&str] = &[ - "ant", "eel", "mole", "sloth", "ape", "emu", "monkey", "snail", "bat", "falcon", "mouse", - "snake", "bear", "fish", "otter", "spider", "bee", "fly", "parrot", "squid", "bird", "fox", - "panda", "swan", "bison", "frog", "pig", "tiger", "camel", "gecko", "pigeon", "toad", "cat", - "goat", "pony", "turkey", "cobra", "goose", "pug", "turtle", "crow", "hawk", "rabbit", "viper", - "deer", "horse", "rat", "wasp", "dog", "jaguar", "raven", "whale", "dove", "koala", "seal", - "wolf", "duck", "lion", "shark", "worm", "eagle", "lizard", "sheep", "zebra", -]; - -lazy_static!{ - pub static ref CONVERTER: PastaIdConverter = PastaIdConverter::new(); -} - -/// Convert pasta IDs to names and vice versa -pub struct PastaIdConverter { - names: Vec -} - -impl PastaIdConverter { - pub fn new() -> Self { - let names; - if let Some(names_path) = &ARGS.custom_names { - let names_data = fs::read_to_string(names_path) - .expect("path for the names file should contain a names file"); - names = names_data - .split('\n') - .map(ToOwned::to_owned) - .collect::>(); - } else { - names = ANIMAL_NAMES - .iter() - .copied() - .map(ToOwned::to_owned) - .collect(); - } - - Self { names } - } - - pub fn to_names(&self, mut number: u64) -> String { - let mut result: Vec<&str> = Vec::new(); - - if number == 0 { - return self.names[0].parse().unwrap(); - } - - let mut power = 6; - - loop { - let digit = number / self.names.len().pow(power) as u64; - if !(result.is_empty() && digit == 0) { - result.push(&self.names[digit as usize]); - } - number -= digit * self.names.len().pow(power) as u64; - if power > 0 { - power -= 1; - } else if power == 0 || number == 0 { - break; - } - } - - result.join("-") - } - - pub fn to_u64(&self, pasta_id: &str) -> Result { - let mut result: u64 = 0; - - let names: Vec<&str> = pasta_id.split('-').collect(); - - let mut pow = names.len(); - for name in names { - pow -= 1; - let name_index = self.names.iter().position(|r| r == name); - match name_index { - None => return Err("Failed to convert animal name to u64!"), - Some(_) => { - result += (name_index.unwrap() * self.names.len().pow(pow as u32)) as u64 - } - } - } - - Ok(result) - } -} - -impl Default for PastaIdConverter { - fn default() -> Self { - Self::new() - } -} - - diff --git a/src/util/syntaxhighlighter.rs b/src/util/syntaxhighlighter.rs index bda6474..4b3f2a2 100644 --- a/src/util/syntaxhighlighter.rs +++ b/src/util/syntaxhighlighter.rs @@ -11,7 +11,7 @@ pub fn html_highlight(text: &str, extension: &str) -> String { let syntax = ps .find_syntax_by_extension(extension) - .or_else(|| Option::from(ps.find_syntax_plain_text())) + .or(Option::from(ps.find_syntax_plain_text())) .unwrap(); let mut h = HighlightLines::new(syntax, &ts.themes["InspiredGitHub"]); @@ -25,7 +25,7 @@ pub fn html_highlight(text: &str, extension: &str) -> String { let mut highlighted_content2: String = String::from(""); for line in highlighted_content.lines() { - highlighted_content2 += &*format!("{line}\n"); + highlighted_content2 += &*format!("{}\n", line); } // Rewrite colours to ones that are compatible with water.css and both light/dark modes @@ -33,5 +33,5 @@ pub fn html_highlight(text: &str, extension: &str) -> String { highlighted_content2 = highlighted_content2.replace("style=\"color:#183691;\"", "style=\"color:blue;\""); - highlighted_content2 + return highlighted_content2; } diff --git a/templates/assets/favicon.svg b/templates/assets/favicon.svg index fb63341..9a306c0 100644 --- a/templates/assets/favicon.svg +++ b/templates/assets/favicon.svg @@ -1,81 +1,5 @@ - - - - - - - - - - - - - - - - - - - + + + + diff --git a/templates/assets/logo.png b/templates/assets/logo.png index 37cbc86..e4e2a30 100644 Binary files a/templates/assets/logo.png and b/templates/assets/logo.png differ diff --git a/templates/assets/logo.svg b/templates/assets/logo.svg deleted file mode 100644 index ca349e6..0000000 --- a/templates/assets/logo.svg +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/templates/assets/water.css b/templates/assets/water.css index 422b511..bc7ec46 100644 --- a/templates/assets/water.css +++ b/templates/assets/water.css @@ -1,626 +1 @@ -/* - * This is (basically) water.css. - * - * repo: https://github.com/kognise/water.css - * - * The license: - * - * The MIT License (MIT) - * - * Copyright © 2019 Kognise - * - * 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. - */ -:root { - --background-body:#4a4a55; - --background:#383844; - --background-alt:#242438; - --selection:#23bf7c; - --text-main:#dfdfef; - --text-bright:#f7f7ff; - --text-muted:#878797; - --links:#28db8f; - --focus:#299465df; - --border:#676773; - --code:var(--text-main); - --animation-duration:0.1s; - --button-base:#299465; - --button-hover:#23bf7c; - --scrollbar-thumb:var(--button-hover); - --scrollbar-thumb-hover:#000; - --form-placeholder:#a9a9a9; - --form-text:#fff; - --variable:#d941e2; - --highlight:#efdb43; - --select-arrow:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' height='63' width='117' fill='%23efefef'%3E%3Cpath d='M115 2c-1-2-4-2-5 0L59 53 7 2a4 4 0 00-5 5l54 54 2 2 3-2 54-54c2-1 2-4 0-5z'/%3E%3C/svg%3E") -} -html { - scrollbar-color:#040a0f #202b38; - scrollbar-color:var(--scrollbar-thumb) var(--background-body); - scrollbar-width:thin -} -body { - font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,Segoe UI Emoji,Apple Color Emoji,Noto Color Emoji,sans-serif; - line-height:1.4; - max-width:800px; - margin:20px auto; - padding:0 10px; - word-wrap:break-word; - color:#dbdbdb; - color:var(--text-main); - background:#202b38; - background:var(--background-body); - text-rendering:optimizeLegibility -} -button, -input, -textarea { - transition:background-color .1s linear,border-color .1s linear,color .1s linear,box-shadow .1s linear,transform .1s ease; - transition:background-color var(--animation-duration) linear,border-color var(--animation-duration) linear,color var(--animation-duration) linear,box-shadow var(--animation-duration) linear,transform var(--animation-duration) ease -} -h1 { - font-size:2.2em; - margin-top:0 -} -h1, -h2, -h3, -h4, -h5, -h6 { - margin-bottom:12px; - margin-top:24px -} -h1, -h2, -h3, -h4, -h5, -h6, -strong { - color:#fff; - color:var(--text-bright) -} -b, -h1, -h2, -h3, -h4, -h5, -h6, -strong, -th { - font-weight:600 -} -q:after, -q:before { - content:none -} -blockquote, -q { - border-left:4px solid rgba(0,150,191,.67); - border-left:4px solid var(--focus); - margin:1.5em 0; - padding:.5em 1em; - font-style:italic -} -blockquote>footer { - font-style:normal; - border:0 -} -address, -blockquote cite { - font-style:normal -} -a[href^=mailto\:]:before { - content:"📧 " -} -a[href^=tel\:]:before { - content:"📞 " -} -a[href^=sms\:]:before { - content:"💬 " -} -mark { - background-color:#efdb43; - background-color:var(--highlight); - border-radius:2px; - padding:0 2px; - color:#000 -} -a>code, -a>strong { - color:inherit -} -button, -input[type=button], -input[type=checkbox], -input[type=radio], -input[type=range], -input[type=reset], -input[type=submit], -select { - cursor:pointer -} -input, -select { - display:block -} -[type=checkbox], -[type=radio] { - display:initial -} -button, -input, -select, -textarea { - color:#fff; - color:var(--form-text); - background-color:#161f27; - background-color:var(--background); - font-family:inherit; - font-size:inherit; - margin-right:6px; - margin-bottom:6px; - padding:10px; - border:none; - border-radius:6px; - outline:none -} -button, -input[type=button], -input[type=reset], -input[type=submit] { - background-color:#0c151c; - background-color:var(--button-base); - padding-right:30px; - padding-left:30px -} -button:hover, -input[type=button]:hover, -input[type=reset]:hover, -input[type=submit]:hover { - background:#040a0f; - background:var(--button-hover) -} -input[type=color] { - min-height:2rem; - padding:8px; - cursor:pointer -} -input[type=checkbox], -input[type=radio] { - height:1em; - width:1em -} -input[type=radio] { - border-radius:100% -} -input { - vertical-align:top -} -label { - vertical-align:middle; - margin-bottom:4px; - display:inline-block -} -button, -input:not([type=checkbox]):not([type=radio]), -input[type=range], -select, -textarea { - -webkit-appearance:none -} -textarea { - display:block; - margin-right:0; - box-sizing:border-box; - resize:vertical -} -textarea:not([cols]) { - width:100% -} -textarea:not([rows]) { - min-height:40px; - height:140px -} -select { - background:#161f27 url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' height='63' width='117' fill='%23efefef'%3E%3Cpath d='M115 2c-1-2-4-2-5 0L59 53 7 2a4 4 0 00-5 5l54 54 2 2 3-2 54-54c2-1 2-4 0-5z'/%3E%3C/svg%3E") calc(100% - 12px) 50%/12px no-repeat; - background:var(--background) var(--select-arrow) calc(100% - 12px) 50%/12px no-repeat; - padding-right:35px -} -select::-ms-expand { - display:none -} -select[multiple] { - padding-right:10px; - background-image:none; - overflow-y:auto -} -button:focus, -input:focus, -select:focus, -textarea:focus { - box-shadow:0 0 0 2px rgba(0,150,191,.67); - box-shadow:0 0 0 2px var(--focus) -} -button:active, -input[type=button]:active, -input[type=checkbox]:active, -input[type=radio]:active, -input[type=range]:active, -input[type=reset]:active, -input[type=submit]:active { - transform:translateY(2px) -} -button:disabled, -input:disabled, -select:disabled, -textarea:disabled { - cursor:not-allowed; - opacity:.5 -} -::-moz-placeholder { - color:#a9a9a9; - color:var(--form-placeholder) -} -:-ms-input-placeholder { - color:#a9a9a9; - color:var(--form-placeholder) -} -::-ms-input-placeholder { - color:#a9a9a9; - color:var(--form-placeholder) -} -::placeholder { - color:#a9a9a9; - color:var(--form-placeholder) -} -fieldset { - border:1px solid rgba(0,150,191,.67); - border:1px solid var(--focus); - border-radius:6px; - margin:0 0 12px; - padding:10px -} -legend { - font-size:.9em; - font-weight:600 -} -input[type=range] { - margin:10px 0; - padding:10px 0; - background:transparent -} -input[type=range]:focus { - outline:none -} -input[type=range]::-webkit-slider-runnable-track { - width:100%; - height:9.5px; - -webkit-transition:.2s; - transition:.2s; - background:#161f27; - background:var(--background); - border-radius:3px -} -input[type=range]::-webkit-slider-thumb { - box-shadow:0 1px 1px #000,0 0 1px #0d0d0d; - height:20px; - width:20px; - border-radius:50%; - background:#526980; - background:var(--border); - -webkit-appearance:none; - margin-top:-7px -} -input[type=range]:focus::-webkit-slider-runnable-track { - background:#161f27; - background:var(--background) -} -input[type=range]::-moz-range-track { - width:100%; - height:9.5px; - -moz-transition:.2s; - transition:.2s; - background:#161f27; - background:var(--background); - border-radius:3px -} -input[type=range]::-moz-range-thumb { - box-shadow:1px 1px 1px #000,0 0 1px #0d0d0d; - height:20px; - width:20px; - border-radius:50%; - background:#526980; - background:var(--border) -} -input[type=range]::-ms-track { - width:100%; - height:9.5px; - background:transparent; - border-color:transparent; - border-width:16px 0; - color:transparent -} -input[type=range]::-ms-fill-lower, -input[type=range]::-ms-fill-upper { - background:#161f27; - background:var(--background); - border:.2px solid #010101; - border-radius:3px; - box-shadow:1px 1px 1px #000,0 0 1px #0d0d0d -} -input[type=range]::-ms-thumb { - box-shadow:1px 1px 1px #000,0 0 1px #0d0d0d; - border:1px solid #000; - height:20px; - width:20px; - border-radius:50%; - background:#526980; - background:var(--border) -} -input[type=range]:focus::-ms-fill-lower, -input[type=range]:focus::-ms-fill-upper { - background:#161f27; - background:var(--background) -} -a { - text-decoration:none; - color:#41adff; - color:var(--links) -} -a:hover { - text-decoration:underline -} -code, -samp, -time { - background:#161f27; - background:var(--background); - color:#ffbe85; - color:var(--code); - padding:2.5px 5px; - border-radius:6px; - font-size:1em -} -pre>code { - padding:10px; - display:block; - overflow-x:auto -} -var { - color:#d941e2; - color:var(--variable); - font-style:normal; - font-family:monospace -} -kbd { - background:#161f27; - background:var(--background); - border:1px solid #526980; - border:1px solid var(--border); - border-radius:2px; - color:#dbdbdb; - color:var(--text-main); - padding:2px 4px -} -img, -video { - max-width:100%; - height:auto -} -hr { - border:none; - border-top:1px solid #526980; - border-top:1px solid var(--border) -} -table { - border-collapse:collapse; - margin-bottom:10px; - width:100%; - table-layout:fixed -} -table caption, -td, -th { - text-align:left -} -td, -th { - padding:6px; - vertical-align:top; - word-wrap:break-word -} -thead { - border-bottom:1px solid #526980; - border-bottom:1px solid var(--border) -} -tfoot { - border-top:1px solid #526980; - border-top:1px solid var(--border) -} -tbody tr:nth-child(2n) { - background-color:#161f27; - background-color:var(--background) -} -tbody tr:nth-child(2n) button { - background-color:#1a242f; - background-color:var(--background-alt) -} -tbody tr:nth-child(2n) button:hover { - background-color:#202b38; - background-color:var(--background-body) -} -::-webkit-scrollbar { - height:10px; - width:10px -} -::-webkit-scrollbar-track { - background:#161f27; - background:var(--background); - border-radius:6px -} -::-webkit-scrollbar-thumb { - background:#040a0f; - background:var(--scrollbar-thumb); - border-radius:6px -} -::-webkit-scrollbar-thumb:hover { - background:#000; - background:var(--scrollbar-thumb-hover) -} -::-moz-selection { - background-color:#1c76c5; - background-color:var(--selection); - color:#fff; - color:var(--text-bright) -} -::selection { - background-color:#1c76c5; - background-color:var(--selection); - color:#fff; - color:var(--text-bright) -} -details { - display:flex; - flex-direction:column; - align-items:flex-start; - background-color:#1a242f; - background-color:var(--background-alt); - padding:10px 10px 0; - margin:1em 0; - border-radius:6px; - overflow:hidden -} -details[open] { - padding:10px -} -details>:last-child { - margin-bottom:0 -} -details[open] summary { - margin-bottom:10px -} -summary { - display:list-item; - background-color:#161f27; - background-color:var(--background); - padding:10px; - margin:-10px -10px 0; - cursor:pointer; - outline:none -} -summary:focus, -summary:hover { - text-decoration:underline -} -details>:not(summary) { - margin-top:0 -} -summary::-webkit-details-marker { - color:#dbdbdb; - color:var(--text-main) -} -dialog { - background-color:#1a242f; - background-color:var(--background-alt); - color:#dbdbdb; - color:var(--text-main); - border-radius:6px; - border:#526980; - border-color:var(--border); - padding:10px 30px -} -dialog>header:first-child { - background-color:#161f27; - background-color:var(--background); - border-radius:6px 6px 0 0; - margin:-10px -30px 10px; - padding:10px; - text-align:center -} -dialog::-webkit-backdrop { - background:rgba(0,0,0,.61); - -webkit-backdrop-filter:blur(4px); - backdrop-filter:blur(4px) -} -dialog::backdrop { - background:rgba(0,0,0,.61); - -webkit-backdrop-filter:blur(4px); - backdrop-filter:blur(4px) -} -footer { - border-top:1px solid #526980; - border-top:1px solid var(--border); - padding-top:10px; - color:#a9b1ba; - color:var(--text-muted) -} -body>footer { - margin-top:40px -} -@media print { - body, - button, - code, - details, - input, - pre, - summary, - textarea { - background-color:#fff - } - button, - input, - textarea { - border:1px solid #000 - } - body, - button, - code, - footer, - h1, - h2, - h3, - h4, - h5, - h6, - input, - pre, - strong, - summary, - textarea { - color:#000 - } - summary::marker { - color:#000 - } - summary::-webkit-details-marker { - color:#000 - } - tbody tr:nth-child(2n) { - background-color:#f2f2f2 - } - a { - color:#00f; - text-decoration:underline - } -} - +:root{--background-body:#fff;--background:#efefef;--background-alt:#f7f7f7;--selection:#9e9e9e;--text-main:#363636;--text-bright:#000;--text-muted:#70777f;--links:#0076d1;--focus:rgba(0,150,191,0.67);--border:#dbdbdb;--code:#000;--animation-duration:0.1s;--button-base:#d0cfcf;--button-hover:#9b9b9b;--scrollbar-thumb:#aaa;--scrollbar-thumb-hover:var(--button-hover);--form-placeholder:#949494;--form-text:#1d1d1d;--variable:#39a33c;--highlight:#ff0;--select-arrow:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' height='63' width='117' fill='%23161f27'%3E%3Cpath d='M115 2c-1-2-4-2-5 0L59 53 7 2a4 4 0 00-5 5l54 54 2 2 3-2 54-54c2-1 2-4 0-5z'/%3E%3C/svg%3E")}@media (prefers-color-scheme:dark){:root{--background-body:#202b38;--background:#161f27;--background-alt:#1a242f;--selection:#1c76c5;--text-main:#dbdbdb;--text-bright:#fff;--text-muted:#a9b1ba;--links:#41adff;--focus:rgba(0,150,191,0.67);--border:#526980;--code:#ffbe85;--animation-duration:0.1s;--button-base:#0c151c;--button-hover:#040a0f;--scrollbar-thumb:var(--button-hover);--scrollbar-thumb-hover:#000;--form-placeholder:#a9a9a9;--form-text:#fff;--variable:#d941e2;--highlight:#efdb43;--select-arrow:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' height='63' width='117' fill='%23efefef'%3E%3Cpath d='M115 2c-1-2-4-2-5 0L59 53 7 2a4 4 0 00-5 5l54 54 2 2 3-2 54-54c2-1 2-4 0-5z'/%3E%3C/svg%3E")}}html{scrollbar-color:#aaa #fff;scrollbar-color:var(--scrollbar-thumb) var(--background-body);scrollbar-width:thin}@media (prefers-color-scheme:dark){html{scrollbar-color:#040a0f #202b38;scrollbar-color:var(--scrollbar-thumb) var(--background-body)}}body{font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,Segoe UI Emoji,Apple Color Emoji,Noto Color Emoji,sans-serif;line-height:1.4;max-width:800px;margin:20px auto;padding:0 10px;word-wrap:break-word;color:#363636;color:var(--text-main);background:#fff;background:var(--background-body);text-rendering:optimizeLegibility}@media (prefers-color-scheme:dark){body{background:#202b38;background:var(--background-body);color:#dbdbdb;color:var(--text-main)}}button{transition:background-color .1s linear,border-color .1s linear,color .1s linear,box-shadow .1s linear,transform .1s ease;transition:background-color var(--animation-duration) linear,border-color var(--animation-duration) linear,color var(--animation-duration) linear,box-shadow var(--animation-duration) linear,transform var(--animation-duration) ease}@media (prefers-color-scheme:dark){button{transition:background-color .1s linear,border-color .1s linear,color .1s linear,box-shadow .1s linear,transform .1s ease;transition:background-color var(--animation-duration) linear,border-color var(--animation-duration) linear,color var(--animation-duration) linear,box-shadow var(--animation-duration) linear,transform var(--animation-duration) ease}}input{transition:background-color .1s linear,border-color .1s linear,color .1s linear,box-shadow .1s linear,transform .1s ease;transition:background-color var(--animation-duration) linear,border-color var(--animation-duration) linear,color var(--animation-duration) linear,box-shadow var(--animation-duration) linear,transform var(--animation-duration) ease}@media (prefers-color-scheme:dark){input{transition:background-color .1s linear,border-color .1s linear,color .1s linear,box-shadow .1s linear,transform .1s ease;transition:background-color var(--animation-duration) linear,border-color var(--animation-duration) linear,color var(--animation-duration) linear,box-shadow var(--animation-duration) linear,transform var(--animation-duration) ease}}textarea{transition:background-color .1s linear,border-color .1s linear,color .1s linear,box-shadow .1s linear,transform .1s ease;transition:background-color var(--animation-duration) linear,border-color var(--animation-duration) linear,color var(--animation-duration) linear,box-shadow var(--animation-duration) linear,transform var(--animation-duration) ease}@media (prefers-color-scheme:dark){textarea{transition:background-color .1s linear,border-color .1s linear,color .1s linear,box-shadow .1s linear,transform .1s ease;transition:background-color var(--animation-duration) linear,border-color var(--animation-duration) linear,color var(--animation-duration) linear,box-shadow var(--animation-duration) linear,transform var(--animation-duration) ease}}h1{font-size:2.2em;margin-top:0}h1,h2,h3,h4,h5,h6{margin-bottom:12px;margin-top:24px}h1{color:#000;color:var(--text-bright)}@media (prefers-color-scheme:dark){h1{color:#fff;color:var(--text-bright)}}h2{color:#000;color:var(--text-bright)}@media (prefers-color-scheme:dark){h2{color:#fff;color:var(--text-bright)}}h3{color:#000;color:var(--text-bright)}@media (prefers-color-scheme:dark){h3{color:#fff;color:var(--text-bright)}}h4{color:#000;color:var(--text-bright)}@media (prefers-color-scheme:dark){h4{color:#fff;color:var(--text-bright)}}h5{color:#000;color:var(--text-bright)}@media (prefers-color-scheme:dark){h5{color:#fff;color:var(--text-bright)}}h6{color:#000;color:var(--text-bright)}@media (prefers-color-scheme:dark){h6{color:#fff;color:var(--text-bright)}}strong{color:#000;color:var(--text-bright)}@media (prefers-color-scheme:dark){strong{color:#fff;color:var(--text-bright)}}b,h1,h2,h3,h4,h5,h6,strong,th{font-weight:600}q:after,q:before{content:none}blockquote{border-left:4px solid rgba(0,150,191,.67);border-left:4px solid var(--focus);margin:1.5em 0;padding:.5em 1em;font-style:italic}@media (prefers-color-scheme:dark){blockquote{border-left:4px solid rgba(0,150,191,.67);border-left:4px solid var(--focus)}}q{border-left:4px solid rgba(0,150,191,.67);border-left:4px solid var(--focus);margin:1.5em 0;padding:.5em 1em;font-style:italic}@media (prefers-color-scheme:dark){q{border-left:4px solid rgba(0,150,191,.67);border-left:4px solid var(--focus)}}blockquote>footer{font-style:normal;border:0}address,blockquote cite{font-style:normal}a[href^=mailto\:]:before{content:"📧 "}a[href^=tel\:]:before{content:"📞 "}a[href^=sms\:]:before{content:"💬 "}mark{background-color:#ff0;background-color:var(--highlight);border-radius:2px;padding:0 2px;color:#000}@media (prefers-color-scheme:dark){mark{background-color:#efdb43;background-color:var(--highlight)}}a>code,a>strong{color:inherit}button,input[type=button],input[type=checkbox],input[type=radio],input[type=range],input[type=reset],input[type=submit],select{cursor:pointer}input,select{display:block}[type=checkbox],[type=radio]{display:initial}input{color:#1d1d1d;color:var(--form-text);background-color:#efefef;background-color:var(--background);font-family:inherit;font-size:inherit;margin-right:6px;margin-bottom:6px;padding:10px;border:none;border-radius:6px;outline:none}@media (prefers-color-scheme:dark){input{background-color:#161f27;background-color:var(--background);color:#fff;color:var(--form-text)}}button{color:#1d1d1d;color:var(--form-text);background-color:#efefef;background-color:var(--background);font-family:inherit;font-size:inherit;margin-right:6px;margin-bottom:6px;padding:10px;border:none;border-radius:6px;outline:none}@media (prefers-color-scheme:dark){button{background-color:#161f27;background-color:var(--background);color:#fff;color:var(--form-text)}}textarea{color:#1d1d1d;color:var(--form-text);background-color:#efefef;background-color:var(--background);font-family:inherit;font-size:inherit;margin-right:6px;margin-bottom:6px;padding:10px;border:none;border-radius:6px;outline:none}@media (prefers-color-scheme:dark){textarea{background-color:#161f27;background-color:var(--background);color:#fff;color:var(--form-text)}}select{color:#1d1d1d;color:var(--form-text);background-color:#efefef;background-color:var(--background);font-family:inherit;font-size:inherit;margin-right:6px;margin-bottom:6px;padding:10px;border:none;border-radius:6px;outline:none}@media (prefers-color-scheme:dark){select{background-color:#161f27;background-color:var(--background);color:#fff;color:var(--form-text)}}button{background-color:#d0cfcf;background-color:var(--button-base);padding-right:30px;padding-left:30px}@media (prefers-color-scheme:dark){button{background-color:#0c151c;background-color:var(--button-base)}}input[type=submit]{background-color:#d0cfcf;background-color:var(--button-base);padding-right:30px;padding-left:30px}@media (prefers-color-scheme:dark){input[type=submit]{background-color:#0c151c;background-color:var(--button-base)}}input[type=reset]{background-color:#d0cfcf;background-color:var(--button-base);padding-right:30px;padding-left:30px}@media (prefers-color-scheme:dark){input[type=reset]{background-color:#0c151c;background-color:var(--button-base)}}input[type=button]{background-color:#d0cfcf;background-color:var(--button-base);padding-right:30px;padding-left:30px}@media (prefers-color-scheme:dark){input[type=button]{background-color:#0c151c;background-color:var(--button-base)}}button:hover{background:#9b9b9b;background:var(--button-hover)}@media (prefers-color-scheme:dark){button:hover{background:#040a0f;background:var(--button-hover)}}input[type=submit]:hover{background:#9b9b9b;background:var(--button-hover)}@media (prefers-color-scheme:dark){input[type=submit]:hover{background:#040a0f;background:var(--button-hover)}}input[type=reset]:hover{background:#9b9b9b;background:var(--button-hover)}@media (prefers-color-scheme:dark){input[type=reset]:hover{background:#040a0f;background:var(--button-hover)}}input[type=button]:hover{background:#9b9b9b;background:var(--button-hover)}@media (prefers-color-scheme:dark){input[type=button]:hover{background:#040a0f;background:var(--button-hover)}}input[type=color]{min-height:2rem;padding:8px;cursor:pointer}input[type=checkbox],input[type=radio]{height:1em;width:1em}input[type=radio]{border-radius:100%}input{vertical-align:top}label{vertical-align:middle;margin-bottom:4px;display:inline-block}button,input:not([type=checkbox]):not([type=radio]),input[type=range],select,textarea{-webkit-appearance:none}textarea{display:block;margin-right:0;box-sizing:border-box;resize:vertical}textarea:not([cols]){width:100%}textarea:not([rows]){min-height:40px;height:140px}select{background:#efefef url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' height='63' width='117' fill='%23161f27'%3E%3Cpath d='M115 2c-1-2-4-2-5 0L59 53 7 2a4 4 0 00-5 5l54 54 2 2 3-2 54-54c2-1 2-4 0-5z'/%3E%3C/svg%3E") calc(100% - 12px) 50%/12px no-repeat;background:var(--background) var(--select-arrow) calc(100% - 12px) 50%/12px no-repeat;padding-right:35px}@media (prefers-color-scheme:dark){select{background:#161f27 url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' height='63' width='117' fill='%23efefef'%3E%3Cpath d='M115 2c-1-2-4-2-5 0L59 53 7 2a4 4 0 00-5 5l54 54 2 2 3-2 54-54c2-1 2-4 0-5z'/%3E%3C/svg%3E") calc(100% - 12px) 50%/12px no-repeat;background:var(--background) var(--select-arrow) calc(100% - 12px) 50%/12px no-repeat}}select::-ms-expand{display:none}select[multiple]{padding-right:10px;background-image:none;overflow-y:auto}input:focus{box-shadow:0 0 0 2px rgba(0,150,191,.67);box-shadow:0 0 0 2px var(--focus)}@media (prefers-color-scheme:dark){input:focus{box-shadow:0 0 0 2px rgba(0,150,191,.67);box-shadow:0 0 0 2px var(--focus)}}select:focus{box-shadow:0 0 0 2px rgba(0,150,191,.67);box-shadow:0 0 0 2px var(--focus)}@media (prefers-color-scheme:dark){select:focus{box-shadow:0 0 0 2px rgba(0,150,191,.67);box-shadow:0 0 0 2px var(--focus)}}button:focus{box-shadow:0 0 0 2px rgba(0,150,191,.67);box-shadow:0 0 0 2px var(--focus)}@media (prefers-color-scheme:dark){button:focus{box-shadow:0 0 0 2px rgba(0,150,191,.67);box-shadow:0 0 0 2px var(--focus)}}textarea:focus{box-shadow:0 0 0 2px rgba(0,150,191,.67);box-shadow:0 0 0 2px var(--focus)}@media (prefers-color-scheme:dark){textarea:focus{box-shadow:0 0 0 2px rgba(0,150,191,.67);box-shadow:0 0 0 2px var(--focus)}}button:active,input[type=button]:active,input[type=checkbox]:active,input[type=radio]:active,input[type=range]:active,input[type=reset]:active,input[type=submit]:active{transform:translateY(2px)}button:disabled,input:disabled,select:disabled,textarea:disabled{cursor:not-allowed;opacity:.5}::-moz-placeholder{color:#949494;color:var(--form-placeholder)}:-ms-input-placeholder{color:#949494;color:var(--form-placeholder)}::-ms-input-placeholder{color:#949494;color:var(--form-placeholder)}::placeholder{color:#949494;color:var(--form-placeholder)}@media (prefers-color-scheme:dark){::-moz-placeholder{color:#a9a9a9;color:var(--form-placeholder)}:-ms-input-placeholder{color:#a9a9a9;color:var(--form-placeholder)}::-ms-input-placeholder{color:#a9a9a9;color:var(--form-placeholder)}::placeholder{color:#a9a9a9;color:var(--form-placeholder)}}fieldset{border:1px solid rgba(0,150,191,.67);border:1px solid var(--focus);border-radius:6px;margin:0 0 12px;padding:10px}@media (prefers-color-scheme:dark){fieldset{border:1px solid rgba(0,150,191,.67);border:1px solid var(--focus)}}legend{font-size:.9em;font-weight:600}input[type=range]{margin:10px 0;padding:10px 0;background:transparent}input[type=range]:focus{outline:none}input[type=range]::-webkit-slider-runnable-track{width:100%;height:9.5px;-webkit-transition:.2s;transition:.2s;background:#efefef;background:var(--background);border-radius:3px}@media (prefers-color-scheme:dark){input[type=range]::-webkit-slider-runnable-track{background:#161f27;background:var(--background)}}input[type=range]::-webkit-slider-thumb{box-shadow:0 1px 1px #000,0 0 1px #0d0d0d;height:20px;width:20px;border-radius:50%;background:#dbdbdb;background:var(--border);-webkit-appearance:none;margin-top:-7px}@media (prefers-color-scheme:dark){input[type=range]::-webkit-slider-thumb{background:#526980;background:var(--border)}}input[type=range]:focus::-webkit-slider-runnable-track{background:#efefef;background:var(--background)}@media (prefers-color-scheme:dark){input[type=range]:focus::-webkit-slider-runnable-track{background:#161f27;background:var(--background)}}input[type=range]::-moz-range-track{width:100%;height:9.5px;-moz-transition:.2s;transition:.2s;background:#efefef;background:var(--background);border-radius:3px}@media (prefers-color-scheme:dark){input[type=range]::-moz-range-track{background:#161f27;background:var(--background)}}input[type=range]::-moz-range-thumb{box-shadow:1px 1px 1px #000,0 0 1px #0d0d0d;height:20px;width:20px;border-radius:50%;background:#dbdbdb;background:var(--border)}@media (prefers-color-scheme:dark){input[type=range]::-moz-range-thumb{background:#526980;background:var(--border)}}input[type=range]::-ms-track{width:100%;height:9.5px;background:transparent;border-color:transparent;border-width:16px 0;color:transparent}input[type=range]::-ms-fill-lower{background:#efefef;background:var(--background);border:.2px solid #010101;border-radius:3px;box-shadow:1px 1px 1px #000,0 0 1px #0d0d0d}@media (prefers-color-scheme:dark){input[type=range]::-ms-fill-lower{background:#161f27;background:var(--background)}}input[type=range]::-ms-fill-upper{background:#efefef;background:var(--background);border:.2px solid #010101;border-radius:3px;box-shadow:1px 1px 1px #000,0 0 1px #0d0d0d}@media (prefers-color-scheme:dark){input[type=range]::-ms-fill-upper{background:#161f27;background:var(--background)}}input[type=range]::-ms-thumb{box-shadow:1px 1px 1px #000,0 0 1px #0d0d0d;border:1px solid #000;height:20px;width:20px;border-radius:50%;background:#dbdbdb;background:var(--border)}@media (prefers-color-scheme:dark){input[type=range]::-ms-thumb{background:#526980;background:var(--border)}}input[type=range]:focus::-ms-fill-lower{background:#efefef;background:var(--background)}@media (prefers-color-scheme:dark){input[type=range]:focus::-ms-fill-lower{background:#161f27;background:var(--background)}}input[type=range]:focus::-ms-fill-upper{background:#efefef;background:var(--background)}@media (prefers-color-scheme:dark){input[type=range]:focus::-ms-fill-upper{background:#161f27;background:var(--background)}}a{text-decoration:none;color:#0076d1;color:var(--links)}@media (prefers-color-scheme:dark){a{color:#41adff;color:var(--links)}}a:hover{text-decoration:underline}code{background:#efefef;background:var(--background);color:#000;color:var(--code);padding:2.5px 5px;border-radius:6px;font-size:1em}@media (prefers-color-scheme:dark){code{color:#ffbe85;color:var(--code);background:#161f27;background:var(--background)}}samp{background:#efefef;background:var(--background);color:#000;color:var(--code);padding:2.5px 5px;border-radius:6px;font-size:1em}@media (prefers-color-scheme:dark){samp{color:#ffbe85;color:var(--code);background:#161f27;background:var(--background)}}time{background:#efefef;background:var(--background);color:#000;color:var(--code);padding:2.5px 5px;border-radius:6px;font-size:1em}@media (prefers-color-scheme:dark){time{color:#ffbe85;color:var(--code);background:#161f27;background:var(--background)}}pre>code{padding:10px;display:block;overflow-x:auto}var{color:#39a33c;color:var(--variable);font-style:normal;font-family:monospace}@media (prefers-color-scheme:dark){var{color:#d941e2;color:var(--variable)}}kbd{background:#efefef;background:var(--background);border:1px solid #dbdbdb;border:1px solid var(--border);border-radius:2px;color:#363636;color:var(--text-main);padding:2px 4px}@media (prefers-color-scheme:dark){kbd{color:#dbdbdb;color:var(--text-main);border:1px solid #526980;border:1px solid var(--border);background:#161f27;background:var(--background)}}img,video{max-width:100%;height:auto}hr{border:none;border-top:1px solid #dbdbdb;border-top:1px solid var(--border)}@media (prefers-color-scheme:dark){hr{border-top:1px solid #526980;border-top:1px solid var(--border)}}table{border-collapse:collapse;margin-bottom:10px;width:100%;table-layout:fixed}table caption,td,th{text-align:left}td,th{padding:6px;vertical-align:top;word-wrap:break-word}thead{border-bottom:1px solid #dbdbdb;border-bottom:1px solid var(--border)}@media (prefers-color-scheme:dark){thead{border-bottom:1px solid #526980;border-bottom:1px solid var(--border)}}tfoot{border-top:1px solid #dbdbdb;border-top:1px solid var(--border)}@media (prefers-color-scheme:dark){tfoot{border-top:1px solid #526980;border-top:1px solid var(--border)}}tbody tr:nth-child(2n){background-color:#efefef;background-color:var(--background)}@media (prefers-color-scheme:dark){tbody tr:nth-child(2n){background-color:#161f27;background-color:var(--background)}}tbody tr:nth-child(2n) button{background-color:#f7f7f7;background-color:var(--background-alt)}@media (prefers-color-scheme:dark){tbody tr:nth-child(2n) button{background-color:#1a242f;background-color:var(--background-alt)}}tbody tr:nth-child(2n) button:hover{background-color:#fff;background-color:var(--background-body)}@media (prefers-color-scheme:dark){tbody tr:nth-child(2n) button:hover{background-color:#202b38;background-color:var(--background-body)}}::-webkit-scrollbar{height:10px;width:10px}::-webkit-scrollbar-track{background:#efefef;background:var(--background);border-radius:6px}@media (prefers-color-scheme:dark){::-webkit-scrollbar-track{background:#161f27;background:var(--background)}}::-webkit-scrollbar-thumb{background:#aaa;background:var(--scrollbar-thumb);border-radius:6px}@media (prefers-color-scheme:dark){::-webkit-scrollbar-thumb{background:#040a0f;background:var(--scrollbar-thumb)}}::-webkit-scrollbar-thumb:hover{background:#9b9b9b;background:var(--scrollbar-thumb-hover)}@media (prefers-color-scheme:dark){::-webkit-scrollbar-thumb:hover{background:#000;background:var(--scrollbar-thumb-hover)}}::-moz-selection{background-color:#9e9e9e;background-color:var(--selection);color:#000;color:var(--text-bright)}::selection{background-color:#9e9e9e;background-color:var(--selection);color:#000;color:var(--text-bright)}@media (prefers-color-scheme:dark){::-moz-selection{color:#fff;color:var(--text-bright)}::selection{color:#fff;color:var(--text-bright)}}@media (prefers-color-scheme:dark){::-moz-selection{background-color:#1c76c5;background-color:var(--selection)}::selection{background-color:#1c76c5;background-color:var(--selection)}}details{display:flex;flex-direction:column;align-items:flex-start;background-color:#f7f7f7;background-color:var(--background-alt);padding:10px 10px 0;margin:1em 0;border-radius:6px;overflow:hidden}@media (prefers-color-scheme:dark){details{background-color:#1a242f;background-color:var(--background-alt)}}details[open]{padding:10px}details>:last-child{margin-bottom:0}details[open] summary{margin-bottom:10px}summary{display:list-item;background-color:#efefef;background-color:var(--background);padding:10px;margin:-10px -10px 0;cursor:pointer;outline:none}@media (prefers-color-scheme:dark){summary{background-color:#161f27;background-color:var(--background)}}summary:focus,summary:hover{text-decoration:underline}details>:not(summary){margin-top:0}summary::-webkit-details-marker{color:#363636;color:var(--text-main)}@media (prefers-color-scheme:dark){summary::-webkit-details-marker{color:#dbdbdb;color:var(--text-main)}}dialog{background-color:#f7f7f7;background-color:var(--background-alt);color:#363636;color:var(--text-main);border-radius:6px;border:#dbdbdb;border-color:var(--border);padding:10px 30px}@media (prefers-color-scheme:dark){dialog{border-color:#526980;border-color:var(--border);color:#dbdbdb;color:var(--text-main);background-color:#1a242f;background-color:var(--background-alt)}}dialog>header:first-child{background-color:#efefef;background-color:var(--background);border-radius:6px 6px 0 0;margin:-10px -30px 10px;padding:10px;text-align:center}@media (prefers-color-scheme:dark){dialog>header:first-child{background-color:#161f27;background-color:var(--background)}}dialog::-webkit-backdrop{background:rgba(0,0,0,.61);-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}dialog::backdrop{background:rgba(0,0,0,.61);-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}footer{border-top:1px solid #dbdbdb;border-top:1px solid var(--border);padding-top:10px;color:#70777f;color:var(--text-muted)}@media (prefers-color-scheme:dark){footer{color:#a9b1ba;color:var(--text-muted);border-top:1px solid #526980;border-top:1px solid var(--border)}}body>footer{margin-top:40px}@media print{body,button,code,details,input,pre,summary,textarea{background-color:#fff}button,input,textarea{border:1px solid #000}body,button,code,footer,h1,h2,h3,h4,h5,h6,input,pre,strong,summary,textarea{color:#000}summary::marker{color:#000}summary::-webkit-details-marker{color:#000}tbody tr:nth-child(2n){background-color:#f2f2f2}a{color:#00f;text-decoration:underline}} diff --git a/templates/error.html b/templates/error.html index a0c007c..806229e 100644 --- a/templates/error.html +++ b/templates/error.html @@ -1,10 +1,10 @@ {% include "header.html" %}
-

{{ status_code.as_u16() }}

-{{ status_code.canonical_reason().unwrap_or("Unknown error") }} +

404

+Not Found

Go Home

-{% include "footer.html" %} +{% include "footer.html" %} \ No newline at end of file diff --git a/templates/footer.html b/templates/footer.html index dcac7b4..ae7f14e 100644 --- a/templates/footer.html +++ b/templates/footer.html @@ -3,7 +3,7 @@

{% if args.footer_text.as_ref().is_none() %} - Karton by Schrottkatze, based on MicroBin by Dániel Szabó and the FOSS Community. + MicroBin by Dániel Szabó and the FOSS Community. Let's keep the Web compact, accessible and humane! {%- else %} {{ args.footer_text.as_ref().unwrap() }} @@ -14,5 +14,4 @@ - - + \ No newline at end of file diff --git a/templates/header.html b/templates/header.html index 7154bdf..8bd4a5b 100644 --- a/templates/header.html +++ b/templates/header.html @@ -2,7 +2,11 @@ - {{ args.title }} + {% if args.title.as_ref().is_none() %} + MicroBin + {%- else %} + {{ args.title.as_ref().unwrap() }} + {%- endif %} @@ -44,13 +48,11 @@ {% if !args.hide_logo %} - - {%- endif %} - {{ args.title }} + + + {%- endif %} {% if args.title.as_ref().is_none() %} + MicroBin {%- else %} {{ args.title.as_ref().unwrap() }} {%- endif %} New @@ -62,4 +64,4 @@


- {%- endif %} + {%- endif %} \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index cdae99f..2f74907 100644 --- a/templates/index.html +++ b/templates/index.html @@ -35,13 +35,6 @@ {%- endif %} 24 hours - {% if args.default_expiry == "3days" %} - {% if args.default_expiry == "1week" %} Documentation and Help
- Source Code + Source Code
- Feedback + Feedback
Donate and Sponsor @@ -39,4 +39,4 @@
-{% include "footer.html" %} +{% include "footer.html" %} \ No newline at end of file diff --git a/templates/pasta.html b/templates/pasta.html index fa5b885..360425c 100644 --- a/templates/pasta.html +++ b/templates/pasta.html @@ -9,7 +9,7 @@ Copy Redirect {%- endif %} - Raw Text + Raw Text Content {%- endif %} {% if args.qr && args.public_path.to_string() != "" %} @@ -22,7 +22,7 @@
{{pasta.id_as_animals()}} + href="{{ args.public_path }}/pasta/{{pasta.id_as_animals()}}">{{pasta.id_as_animals()}} {% if args.public_path.to_string() != "" %}