Compare commits
30 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
f55a5eba96 | ||
![]() |
011cc25490 | ||
![]() |
d44a3081bc | ||
![]() |
51f7f54be7 | ||
![]() |
a3fc97a460 | ||
![]() |
05941f0d6f | ||
![]() |
7b4cd7c26e | ||
![]() |
f54d5bd780 | ||
![]() |
435c07d75e | ||
![]() |
cc09d1b529 | ||
![]() |
60c3a1f9ac | ||
![]() |
d4d94b61da | ||
![]() |
9053211904 | ||
![]() |
35a512680c | ||
![]() |
bcd620ed43 | ||
![]() |
556f4e87df | ||
![]() |
fa88bce917 | ||
![]() |
a5d326b679 | ||
![]() |
465873e095 | ||
![]() |
39233e9447 | ||
![]() |
738e036cb5 | ||
![]() |
de2cc48f88 | ||
![]() |
0687f44137 | ||
![]() |
fec933c5ec | ||
![]() |
cc2dd1e1fe | ||
![]() |
85ed1b2b92 | ||
![]() |
73ec59ccda | ||
![]() |
f5b9036a2a | ||
![]() |
0d43f2f60a | ||
![]() |
aa9246da4e |
13
.github/FUNDING.yml
vendored
13
.github/FUNDING.yml
vendored
@ -1,13 +0,0 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: szabodanika
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: dani_sz
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name="microbin"
|
||||
version="1.0.2"
|
||||
version="1.1.0"
|
||||
edition="2021"
|
||||
authors = ["Daniel Szabo <daniel.szabo99@outlook.com>"]
|
||||
license = "BSD-3-Clause"
|
||||
@ -18,12 +18,13 @@ actix-web="4"
|
||||
actix-files="0.6.0"
|
||||
serde={ version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0.80"
|
||||
bytesize = { version = "1.1", features = ["serde"] }
|
||||
askama="0.10"
|
||||
askama-filters={ version = "0.1.3", features = ["chrono"] }
|
||||
chrono="0.4.19"
|
||||
rand="0.8.5"
|
||||
linkify="0.8.1"
|
||||
clap={ version = "3.1.12", features = ["derive"] }
|
||||
clap={ version = "3.1.12", features = ["derive", "env"] }
|
||||
actix-multipart = "0.4.0"
|
||||
futures = "0.3"
|
||||
sanitize-filename = "0.3.0"
|
||||
|
@ -19,8 +19,5 @@ WORKDIR /usr/local/bin
|
||||
# copy built exacutable
|
||||
COPY --from=builder /usr/src/microbin/target/release/microbin /usr/local/bin/microbin
|
||||
|
||||
# copy /static folder containing the stylesheets
|
||||
COPY --from=builder /usr/src/microbin/static /usr/local/bin/static
|
||||
|
||||
# run the binary
|
||||
CMD ["microbin"]
|
||||
CMD ["microbin"]
|
||||
|
57
README.MD
57
README.MD
@ -105,11 +105,14 @@ Remember, MicroBin will create your database and file storage wherever you execu
|
||||
|
||||
`cd ~/microbin/`
|
||||
|
||||
`microbin --port 8080 --highlightsyntax --editable`
|
||||
`microbin --highlightsyntax --editable`
|
||||
|
||||
### From AUR (for any Arch-based distro)
|
||||
Install `microbin` package from AUR and start/enable microbin systemd service. Systemd will start server on 127.0.0.1:8080 with almost all features enabled, but this can be changed in `/etc/microbin.conf`.
|
||||
|
||||
### Building MicroBin
|
||||
|
||||
Simply clone the repository, build it with `cargo build --release` and run the `microbin` executable in the created `target/release/` directory. It will start on port 8080. You can change the port with `-p` or `--port` CL arguments. For other arguments see [the Wiki](https://github.com/szabodanika/microbin/wiki).
|
||||
Simply clone the repository, build it with `cargo build --release` and run the `microbin` executable in the created `target/release/` directory. It will start listening on 0.0.0.0:8080. You can change the port or bind address with CL arguments `-p (--port)` or `-b (--bind)` respectively . For other arguments see [the Wiki](https://github.com/szabodanika/microbin/wiki).
|
||||
|
||||
```
|
||||
git clone https://github.com/szabodanika/microbin.git
|
||||
@ -118,6 +121,49 @@ cargo build --release
|
||||
./target/release/microbin -p 80
|
||||
```
|
||||
|
||||
### Building Docker Image
|
||||
|
||||
MicroBin includes a Dockerfile. To build the image, follow these steps:
|
||||
|
||||
```
|
||||
git clone https://github.com/szabodanika/microbin.git
|
||||
cd microbin
|
||||
docker build -t microbin-docker .
|
||||
```
|
||||
|
||||
Then, for `docker compose` you can repurpose the following example in your compose file:
|
||||
|
||||
```
|
||||
services:
|
||||
paste:
|
||||
image: microbin-docker
|
||||
restart: always
|
||||
ports:
|
||||
- "80:8080"
|
||||
volumes:
|
||||
- ./microbin-data:/usr/local/bin/pasta_data
|
||||
```
|
||||
To pass command line arguments you must edit the Dockerfile and change the CMD line. In this example we add the syntax highlighting option and enable private pastas:
|
||||
|
||||
```
|
||||
CMD ["microbin", "--highlightsyntax", "--private"]
|
||||
```
|
||||
You then need to rebuild the image and recreate your container.
|
||||
|
||||
|
||||
**Note:** If you are getting the following error about domain name resolution:
|
||||
|
||||
```
|
||||
warning: spurious network error (2 tries remaining): failed to resolve address for github.com: Temporary failure in name resolution; class=Net (12)
|
||||
warning: spurious network error (1 tries remaining): failed to resolve address for github.com: Temporary failure in name resolution; class=Net (12)
|
||||
```
|
||||
|
||||
You might need to run `docker build` with the `--network` option:
|
||||
|
||||
```
|
||||
docker build --network host -t microbin-docker .
|
||||
```
|
||||
|
||||
### MicroBin as a service
|
||||
|
||||
To install it as a service on your Linux machine, create a file called `/etc/systemd/system/microbin.service`, paste this into it with the `[username]` and `[path to installation directory]` replaced with the actual values. If you installed MicroBin from cargo, your executable will be in your cargo directory, e.g. `/Users/daniel/.cargo/bin/microbin`.
|
||||
@ -247,6 +293,13 @@ Default value: 8080
|
||||
|
||||
Sets the port for the server will be listening on.
|
||||
|
||||
|
||||
### -b, --bind [ADDRESS]
|
||||
|
||||
Default value: 0.0.0.0
|
||||
|
||||
Sets the bind address for the server will be listening on. Both ipv4 and ipv6 are supported.
|
||||
|
||||
### --private
|
||||
|
||||
Enables private pastas. Adds a new checkbox to make your pasta private, which then won't show up on the pastalist page. With the URL to your pasta, it will still be accessible.
|
||||
|
19
SECURITY.md
Normal file
19
SECURITY.md
Normal file
@ -0,0 +1,19 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Use this section to tell people about which versions of your project are
|
||||
currently being supported with security updates.
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 1.1.* | :white_check_mark: |
|
||||
| < 1.1.0 | :x: |
|
||||
|
||||
## 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.
|
40
src/args.rs
40
src/args.rs
@ -1,3 +1,4 @@
|
||||
use std::net::IpAddr;
|
||||
use clap::Parser;
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
@ -8,51 +9,54 @@ lazy_static! {
|
||||
#[derive(Parser, Debug, Clone)]
|
||||
#[clap(author, version, about, long_about = None)]
|
||||
pub struct Args {
|
||||
#[clap(long)]
|
||||
#[clap(long, env="MICROBIN_AUTH_USERNAME")]
|
||||
pub auth_username: Option<String>,
|
||||
|
||||
#[clap(long)]
|
||||
#[clap(long, env="MICROBIN_AUTH_PASSWORD")]
|
||||
pub auth_password: Option<String>,
|
||||
|
||||
#[clap(long)]
|
||||
#[clap(long, env="MICROBIN_EDITABLE")]
|
||||
pub editable: bool,
|
||||
|
||||
#[clap(long)]
|
||||
#[clap(long, env="MICROBIN_FOOTER_TEXT")]
|
||||
pub footer_text: Option<String>,
|
||||
|
||||
#[clap(long)]
|
||||
#[clap(long, env="MICROBIN_HIDE_FOOTER")]
|
||||
pub hide_footer: bool,
|
||||
|
||||
#[clap(long)]
|
||||
#[clap(long, env="MICROBIN_HIDE_HEADER")]
|
||||
pub hide_header: bool,
|
||||
|
||||
#[clap(long)]
|
||||
#[clap(long, env="MICROBIN_HIDE_LOGO")]
|
||||
pub hide_logo: bool,
|
||||
|
||||
#[clap(long)]
|
||||
#[clap(long, env="MICROBIN_NO_LISTING")]
|
||||
pub no_listing: bool,
|
||||
|
||||
#[clap(long)]
|
||||
#[clap(long, env="MICROBIN_HIGHLIGHTSYNTAX")]
|
||||
pub highlightsyntax: bool,
|
||||
|
||||
#[clap(short, long, default_value_t = 8080)]
|
||||
pub port: u32,
|
||||
#[clap(short, long, env="MICROBIN_PORT", default_value_t = 8080)]
|
||||
pub port: u16,
|
||||
|
||||
#[clap(long)]
|
||||
#[clap(short, long, env="MICROBIN_BIND", default_value_t = IpAddr::from([0, 0, 0, 0]))]
|
||||
pub bind: IpAddr,
|
||||
|
||||
#[clap(long, env="MICROBIN_PRIVATE")]
|
||||
pub private: bool,
|
||||
|
||||
#[clap(long)]
|
||||
#[clap(long, env="MICROBIN_PURE_HTML")]
|
||||
pub pure_html: bool,
|
||||
|
||||
#[clap(long)]
|
||||
#[clap(long, env="MICROBIN_READONLY")]
|
||||
pub readonly: bool,
|
||||
|
||||
#[clap(long)]
|
||||
#[clap(long, env="MICROBIN_TITLE")]
|
||||
pub title: Option<String>,
|
||||
|
||||
#[clap(short, long, default_value_t = 1)]
|
||||
#[clap(short, long, env="MICROBIN_THREADS", default_value_t = 1)]
|
||||
pub threads: u8,
|
||||
|
||||
#[clap(long)]
|
||||
#[clap(long, env="MICROBIN_WIDE")]
|
||||
pub wide: bool,
|
||||
}
|
||||
}
|
@ -1,11 +1,14 @@
|
||||
use crate::dbio::save_to_file;
|
||||
use crate::pasta::PastaFile;
|
||||
use crate::util::animalnumbers::to_animal_names;
|
||||
use crate::util::misc::is_valid_url;
|
||||
use crate::{AppState, Pasta, ARGS};
|
||||
use actix_multipart::Multipart;
|
||||
use actix_web::{get, web, Error, HttpResponse, Responder};
|
||||
use askama::Template;
|
||||
use bytesize::ByteSize;
|
||||
use futures::TryStreamExt;
|
||||
use log::warn;
|
||||
use rand::Rng;
|
||||
use std::io::Write;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
@ -46,7 +49,7 @@ pub async fn create(
|
||||
let mut new_pasta = Pasta {
|
||||
id: rand::thread_rng().gen::<u16>() as u64,
|
||||
content: String::from("No Text Content"),
|
||||
file: String::from("no-file"),
|
||||
file: None,
|
||||
extension: String::from(""),
|
||||
private: false,
|
||||
editable: false,
|
||||
@ -103,27 +106,41 @@ pub async fn create(
|
||||
continue;
|
||||
}
|
||||
"file" => {
|
||||
let content_disposition = field.content_disposition();
|
||||
let path = field.content_disposition().get_filename();
|
||||
|
||||
let filename = match content_disposition.get_filename() {
|
||||
let path = match path {
|
||||
Some("") => continue,
|
||||
Some(filename) => filename.replace(' ', "_").to_string(),
|
||||
Some(p) => p,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
std::fs::create_dir_all(format!("./pasta_data/{}", &new_pasta.id_as_animals()))
|
||||
let mut file = match PastaFile::from_unsanitized(&path) {
|
||||
Ok(f) => f,
|
||||
Err(e) => {
|
||||
warn!("Unsafe file name: {e:?}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
std::fs::create_dir_all(format!("./pasta_data/public/{}", &new_pasta.id_as_animals()))
|
||||
.unwrap();
|
||||
|
||||
let filepath = format!("./pasta_data/{}/{}", &new_pasta.id_as_animals(), &filename);
|
||||
|
||||
new_pasta.file = filename;
|
||||
let filepath = format!(
|
||||
"./pasta_data/public/{}/{}",
|
||||
&new_pasta.id_as_animals(),
|
||||
&file.name()
|
||||
);
|
||||
|
||||
let mut f = web::block(|| std::fs::File::create(filepath)).await??;
|
||||
|
||||
let mut size = 0;
|
||||
while let Some(chunk) = field.try_next().await? {
|
||||
size += chunk.len();
|
||||
f = web::block(move || f.write_all(&chunk).map(|_| f)).await??;
|
||||
}
|
||||
|
||||
file.size = ByteSize::b(size as u64);
|
||||
|
||||
new_pasta.file = Some(file);
|
||||
new_pasta.pasta_type = String::from("text");
|
||||
}
|
||||
_ => {}
|
||||
|
@ -2,10 +2,12 @@ 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::misc::remove_expired;
|
||||
use crate::AppState;
|
||||
use askama::Template;
|
||||
use std::fs;
|
||||
|
||||
#[get("/remove/{id}")]
|
||||
pub async fn remove(data: web::Data<AppState>, id: web::Path<String>) -> HttpResponse {
|
||||
@ -19,10 +21,22 @@ pub async fn remove(data: web::Data<AppState>, id: web::Path<String>) -> HttpRes
|
||||
|
||||
let id = to_u64(&*id.into_inner()).unwrap_or(0);
|
||||
|
||||
remove_expired(&mut pastas);
|
||||
|
||||
for (i, pasta) in pastas.iter().enumerate() {
|
||||
if pasta.id == id {
|
||||
// remove the file itself
|
||||
if let Some(PastaFile { name, .. }) = &pasta.file {
|
||||
if fs::remove_file(format!("./pasta_data/public/{}/{}", pasta.id_as_animals(), name))
|
||||
.is_err()
|
||||
{
|
||||
log::error!("Failed to delete file {}!", name)
|
||||
}
|
||||
|
||||
// and remove the containing directory
|
||||
if fs::remove_dir(format!("./pasta_data/public/{}/", pasta.id_as_animals())).is_err() {
|
||||
log::error!("Failed to delete directory {}!", name)
|
||||
}
|
||||
}
|
||||
// remove it from in-memory pasta list
|
||||
pastas.remove(i);
|
||||
return HttpResponse::Found()
|
||||
.append_header(("Location", "/pastalist"))
|
||||
@ -30,6 +44,8 @@ pub async fn remove(data: web::Data<AppState>, id: web::Path<String>) -> HttpRes
|
||||
}
|
||||
}
|
||||
|
||||
remove_expired(&mut pastas);
|
||||
|
||||
HttpResponse::Ok()
|
||||
.content_type("text/html")
|
||||
.body(ErrorTemplate { args: &ARGS }.render().unwrap())
|
||||
|
13
src/main.rs
13
src/main.rs
@ -58,15 +58,16 @@ async fn main() -> std::io::Result<()> {
|
||||
.init();
|
||||
|
||||
log::info!(
|
||||
"MicroBin starting on http://127.0.0.1:{}",
|
||||
"MicroBin starting on http://{}:{}",
|
||||
ARGS.bind.to_string(),
|
||||
ARGS.port.to_string()
|
||||
);
|
||||
|
||||
match fs::create_dir_all("./pasta_data") {
|
||||
match fs::create_dir_all("./pasta_data/public") {
|
||||
Ok(dir) => dir,
|
||||
Err(error) => {
|
||||
log::error!("Couldn't create data directory ./pasta_data: {:?}", error);
|
||||
panic!("Couldn't create data directory ./pasta_data: {:?}", error);
|
||||
log::error!("Couldn't create data directory ./pasta_data/public/: {:?}", error);
|
||||
panic!("Couldn't create data directory ./pasta_data/public/: {:?}", error);
|
||||
}
|
||||
};
|
||||
|
||||
@ -86,7 +87,7 @@ async fn main() -> std::io::Result<()> {
|
||||
.service(edit::get_edit)
|
||||
.service(edit::post_edit)
|
||||
.service(static_resources::static_resources)
|
||||
.service(actix_files::Files::new("/file", "./pasta_data"))
|
||||
.service(actix_files::Files::new("/file", "./pasta_data/public/"))
|
||||
.service(web::resource("/upload").route(web::post().to(create::create)))
|
||||
.default_service(web::route().to(errors::not_found))
|
||||
.wrap(middleware::Logger::default())
|
||||
@ -97,7 +98,7 @@ async fn main() -> std::io::Result<()> {
|
||||
HttpAuthentication::basic(util::auth::auth_validator),
|
||||
))
|
||||
})
|
||||
.bind(format!("0.0.0.0:{}", ARGS.port.to_string()))?
|
||||
.bind((ARGS.bind, ARGS.port))?
|
||||
.workers(ARGS.threads as usize)
|
||||
.run()
|
||||
.await
|
||||
|
40
src/pasta.rs
40
src/pasta.rs
@ -1,16 +1,39 @@
|
||||
use std::fmt;
|
||||
|
||||
use chrono::{DateTime, Datelike, NaiveDateTime, Timelike, Utc};
|
||||
use bytesize::ByteSize;
|
||||
use chrono::{Datelike, Local, TimeZone, Timelike};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::util::animalnumbers::to_animal_names;
|
||||
use crate::util::syntaxhighlighter::html_highlight;
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct PastaFile {
|
||||
pub name: String,
|
||||
pub size: ByteSize,
|
||||
}
|
||||
|
||||
impl PastaFile {
|
||||
pub fn from_unsanitized(path: &str) -> Result<Self, &'static str> {
|
||||
let path = Path::new(path);
|
||||
let name = path.file_name().ok_or("Path did not contain a file name")?;
|
||||
let name = name.to_string_lossy().replace(' ', "_");
|
||||
Ok(Self {
|
||||
name,
|
||||
size: ByteSize::b(0),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Pasta {
|
||||
pub id: u64,
|
||||
pub content: String,
|
||||
pub file: String,
|
||||
pub file: Option<PastaFile>,
|
||||
pub extension: String,
|
||||
pub private: bool,
|
||||
pub editable: bool,
|
||||
@ -25,9 +48,9 @@ impl Pasta {
|
||||
}
|
||||
|
||||
pub fn created_as_string(&self) -> String {
|
||||
let date = DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(self.created, 0), Utc);
|
||||
let date = Local.timestamp(self.created, 0);
|
||||
format!(
|
||||
"{:02}-{:02} {}:{}",
|
||||
"{:02}-{:02} {:02}:{:02}",
|
||||
date.month(),
|
||||
date.day(),
|
||||
date.hour(),
|
||||
@ -39,10 +62,9 @@ impl Pasta {
|
||||
if self.expiration == 0 {
|
||||
String::from("Never")
|
||||
} else {
|
||||
let date =
|
||||
DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(self.expiration, 0), Utc);
|
||||
let date = Local.timestamp(self.expiration, 0);
|
||||
format!(
|
||||
"{:02}-{:02} {}:{}",
|
||||
"{:02}-{:02} {:02}:{:02}",
|
||||
date.month(),
|
||||
date.day(),
|
||||
date.hour(),
|
||||
|
@ -22,20 +22,22 @@ pub fn remove_expired(pastas: &mut Vec<Pasta>) {
|
||||
true
|
||||
} else {
|
||||
// remove the file itself
|
||||
match fs::remove_file(format!("./pasta_data/{}/{}", p.id_as_animals(), p.file)) {
|
||||
Ok(_) => {}
|
||||
Err(_) => {
|
||||
log::error!("Failed to delete file {}!", p.file)
|
||||
if let Some(file) = &p.file {
|
||||
if fs::remove_file(format!(
|
||||
"./pasta_data/public/{}/{}",
|
||||
p.id_as_animals(),
|
||||
file.name()
|
||||
))
|
||||
.is_err()
|
||||
{
|
||||
log::error!("Failed to delete file {}!", file.name())
|
||||
}
|
||||
|
||||
// and remove the containing directory
|
||||
if fs::remove_dir(format!("./pasta_data/public/{}/", p.id_as_animals())).is_err() {
|
||||
log::error!("Failed to delete directory {}!", file.name())
|
||||
}
|
||||
}
|
||||
// and remove the containing directory
|
||||
match fs::remove_dir(format!("./pasta_data/{}/", p.id_as_animals())) {
|
||||
Ok(_) => {}
|
||||
Err(_) => {
|
||||
log::error!("Failed to delete directory {}!", p.file)
|
||||
}
|
||||
}
|
||||
// remove
|
||||
false
|
||||
}
|
||||
});
|
||||
|
@ -1,7 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
{% if args.footer_text.as_ref().is_none() %}
|
||||
{% if args.title.as_ref().is_none() %}
|
||||
<title>MicroBin</title>
|
||||
{%- else %}
|
||||
<title>{{ args.title.as_ref().unwrap() }}</title>
|
||||
@ -42,7 +42,7 @@
|
||||
<i><span style="font-size:2.2rem; margin-right:1rem">μ</span></i>
|
||||
{%- endif %}
|
||||
|
||||
{% if args.footer_text.as_ref().is_none() %}
|
||||
{% if args.title.as_ref().is_none() %}
|
||||
MicroBin
|
||||
{%- else %}
|
||||
{{ args.title.as_ref().unwrap() }}
|
||||
|
@ -1,19 +1,28 @@
|
||||
{% include "header.html" %}
|
||||
<a style="margin-right: 0.5rem" href="/raw/{{pasta.id_as_animals()}}">Raw Text Content</a>
|
||||
{% if pasta.file != "no-file" %}
|
||||
<a style="margin-right: 0.5rem; margin-left: 0.5rem" href="/file/{{pasta.id_as_animals()}}/{{pasta.file}}">Attached file
|
||||
'{{pasta.file}}'</a>
|
||||
{%- endif %}
|
||||
{% if pasta.editable %}
|
||||
<a style="margin-right: 0.5rem; margin-left: 0.5rem" href="/edit/{{pasta.id_as_animals()}}">Edit</a>
|
||||
{%- endif %}
|
||||
<a style="margin-right: 0.5rem; margin-left: 0.5rem" href="/remove/{{pasta.id_as_animals()}}">Remove</a>
|
||||
{% if args.highlightsyntax %}
|
||||
<pre><code>{{pasta.content_syntax_highlighted()}}</code></pre>
|
||||
{%- else %}
|
||||
<pre><code>{{pasta.content_not_highlighted()}}</code></pre>
|
||||
{%- endif %}
|
||||
|
||||
<div style="float: left">
|
||||
<a style="margin-right: 0.5rem" href="/raw/{{pasta.id_as_animals()}}">Raw Text Content</a>
|
||||
{% if pasta.file.is_some() %}
|
||||
<a style="margin-right: 0.5rem; margin-left: 0.5rem"
|
||||
href="/file/{{pasta.id_as_animals()}}/{{pasta.file.as_ref().unwrap().name()}}">
|
||||
Attached file'{{pasta.file.as_ref().unwrap().name()}}' [{{pasta.file.as_ref().unwrap().size}}]
|
||||
</a>
|
||||
{%- endif %}
|
||||
{% if pasta.editable %}
|
||||
<a style="margin-right: 0.5rem; margin-left: 0.5rem" href="/edit/{{pasta.id_as_animals()}}">Edit</a>
|
||||
{%- endif %}
|
||||
<a style="margin-right: 0.5rem; margin-left: 0.5rem" href="/remove/{{pasta.id_as_animals()}}">Remove</a>
|
||||
</div>
|
||||
<div style="float: right">
|
||||
<a href="/pasta/{{pasta.id_as_animals()}}"><i>{{pasta.id_as_animals()}}</i></a>
|
||||
</div>
|
||||
<br>
|
||||
<div style="clear: both;">
|
||||
{% if args.highlightsyntax %}
|
||||
<pre><code>{{pasta.content_syntax_highlighted()}}</code></pre>
|
||||
{%- else %}
|
||||
<pre><code>{{pasta.content_not_highlighted()}}</code></pre>
|
||||
{%- endif %}
|
||||
</div>
|
||||
<style>
|
||||
code-line {
|
||||
counter-increment: listing;
|
||||
|
@ -48,8 +48,8 @@
|
||||
</td>
|
||||
<td>
|
||||
<a style="margin-right:1rem" href="/raw/{{pasta.id_as_animals()}}">Raw</a>
|
||||
{% if pasta.file != "no-file" %}
|
||||
<a style="margin-right:1rem" href="/file/{{pasta.id_as_animals()}}/{{pasta.file}}">File</a>
|
||||
{% if pasta.file.is_some() %}
|
||||
<a style="margin-right:1rem" href="/file/{{pasta.id_as_animals()}}/{{pasta.file.as_ref().unwrap().name()}}">File</a>
|
||||
{%- endif %}
|
||||
{% if pasta.editable %}
|
||||
<a style="margin-right:1rem" href="/edit/{{pasta.id_as_animals()}}">Edit</a>
|
||||
|
Loading…
x
Reference in New Issue
Block a user