@ -0,0 +1,87 @@
|
|||||||
|
from . import config
|
||||||
|
|
||||||
|
import ipaddress
|
||||||
|
from os import path
|
||||||
|
from pathlib import Path
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
########## Common functions ##########
|
||||||
|
|
||||||
|
# Check request IP is in config whitelist
|
||||||
|
def verify_whitelist(ip):
|
||||||
|
address = ipaddress.ip_address(ip)
|
||||||
|
|
||||||
|
# Check blacklist
|
||||||
|
for network in config.blacklist_cidr:
|
||||||
|
if address in ipaddress.IPv4Network(network):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not config.use_whitelist:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Check whitelist
|
||||||
|
for network in config.whitelist_cidr:
|
||||||
|
if address in ipaddress.IPv4Network(network):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Solve anomalies in icon naming
|
||||||
|
def get_icon(language):
|
||||||
|
if language == "C#":
|
||||||
|
return "csharp"
|
||||||
|
elif language == "C++":
|
||||||
|
return "cplusplus"
|
||||||
|
elif language == "Jupyter Notebook":
|
||||||
|
return "jupyter"
|
||||||
|
else:
|
||||||
|
return language.lower()
|
||||||
|
|
||||||
|
# For handling reverse proxy configurations
|
||||||
|
# Note that these are HTTP headers and are generally untrustworthy
|
||||||
|
# Make sure your proxy configuration is either setting or clearing these
|
||||||
|
def get_source_ip(request):
|
||||||
|
if config.behind_proxy:
|
||||||
|
if 'X-Real-IP' in request.headers:
|
||||||
|
return request.headers['X-Real-IP']
|
||||||
|
elif 'X-Forwarded-For' in request.headers:
|
||||||
|
return request.headers['X-Forwarded-For']
|
||||||
|
|
||||||
|
return request.remote_addr
|
||||||
|
|
||||||
|
# Determine theme by checking for cookie, or returning default
|
||||||
|
def set_theme(request):
|
||||||
|
if 'pastey_theme' in request.cookies:
|
||||||
|
return request.cookies['pastey_theme']
|
||||||
|
return config.default_theme
|
||||||
|
|
||||||
|
# Get a sorted list of all themes in the theme dir
|
||||||
|
def get_themes():
|
||||||
|
themes = []
|
||||||
|
for path in Path("./static/themes/").iterdir():
|
||||||
|
themes.append(str(path).split('/')[-1].split('.')[0])
|
||||||
|
return sorted(themes, key=str.casefold)
|
||||||
|
|
||||||
|
# Get file path from unique id
|
||||||
|
# This is a wrapper to check for files with the .expires extension
|
||||||
|
def determine_file(unique_id):
|
||||||
|
attempt = config.data_directory + "/" + unique_id
|
||||||
|
if path.exists(attempt):
|
||||||
|
return attempt
|
||||||
|
|
||||||
|
# Check for expiration format
|
||||||
|
attempt = attempt + ".expires"
|
||||||
|
if path.exists(attempt):
|
||||||
|
return attempt
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Take a paste object and check if it is expired
|
||||||
|
def is_expired(paste):
|
||||||
|
if 'expiration' in paste and paste['expiration'] != "":
|
||||||
|
expires = datetime.strptime(paste['expiration'], "%a, %d %b %Y at %H:%M:%S")
|
||||||
|
|
||||||
|
if expires < datetime.now():
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
@ -0,0 +1,52 @@
|
|||||||
|
# Data directory
|
||||||
|
data_directory = "./data"
|
||||||
|
|
||||||
|
# Listen address
|
||||||
|
listen_address = "0.0.0.0"
|
||||||
|
|
||||||
|
# Listen port
|
||||||
|
listen_port = 5000
|
||||||
|
|
||||||
|
# Use whitelisting
|
||||||
|
# Whitelisted IPs can view recent pastes on the home page, as well as delete pastes
|
||||||
|
# For limiting pasting to whitelisted users, enable the "restrict_pasting" option below
|
||||||
|
use_whitelist = True
|
||||||
|
|
||||||
|
# Whitelist CIDR
|
||||||
|
whitelist_cidr = ['127.0.0.1/32', '10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16']
|
||||||
|
|
||||||
|
# Blacklist CIDR
|
||||||
|
blacklist_cidr = []
|
||||||
|
|
||||||
|
# Restrict pasting functionality to whitelisted IPs
|
||||||
|
restrict_pasting = False
|
||||||
|
|
||||||
|
# Restrict raw pasting to whitelisted IPs
|
||||||
|
restrict_raw_pasting = True
|
||||||
|
|
||||||
|
# Rate limit for pasting (ignored for whitelisted users)
|
||||||
|
rate_limit = "5/hour"
|
||||||
|
|
||||||
|
# Guess threshold for automatic language detection
|
||||||
|
guess_threshold = 0.20
|
||||||
|
|
||||||
|
# Number of recent pastes to show on the home page
|
||||||
|
recent_pastes = 10
|
||||||
|
|
||||||
|
# Try to use X-Real-IP or X-Forwarded-For HTTP headers
|
||||||
|
behind_proxy = False
|
||||||
|
|
||||||
|
# Default theme to display to users
|
||||||
|
default_theme = "Light"
|
||||||
|
|
||||||
|
# Purge interval (in seconds) for checking expired pastes
|
||||||
|
purge_interval = 3600
|
||||||
|
|
||||||
|
# Show recent pastes, even to non-whitelisted users (without a delete button)
|
||||||
|
force_show_recent = False
|
||||||
|
|
||||||
|
# Ignore these classifications for language detection
|
||||||
|
ignore_guess = ['TeX', 'SQL']
|
||||||
|
|
||||||
|
# Show CLI button on home page
|
||||||
|
show_cli_button = True
|
@ -0,0 +1,143 @@
|
|||||||
|
from __main__ import guess, app
|
||||||
|
from . import config, common
|
||||||
|
|
||||||
|
from os import path, remove
|
||||||
|
from pathlib import Path
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from cryptography.fernet import Fernet
|
||||||
|
import time
|
||||||
|
import uuid
|
||||||
|
import json
|
||||||
|
|
||||||
|
########## Paste functions ##########
|
||||||
|
|
||||||
|
# Get recent n pastes, defined in config by recent_pastes
|
||||||
|
def get_recent(limit=config.recent_pastes):
|
||||||
|
paths = sorted(Path(config.data_directory).iterdir(), key=path.getmtime, reverse=True)
|
||||||
|
|
||||||
|
recent_pastes = []
|
||||||
|
i = 0
|
||||||
|
while i < limit and i < len(paths):
|
||||||
|
with open(paths[i]) as fp:
|
||||||
|
paste = json.loads(fp.read())
|
||||||
|
|
||||||
|
basename = path.basename(paths[i])
|
||||||
|
paste['unique_id'] = basename[:-8] if basename.endswith(".expires") else basename
|
||||||
|
paste['content'] = '\n'.join(paste['content'].splitlines()[0:10])
|
||||||
|
paste['icon'] = common.get_icon(paste['language'])
|
||||||
|
|
||||||
|
if paste['encrypted']:
|
||||||
|
paste['content'] = "[Encrypted]"
|
||||||
|
|
||||||
|
recent_pastes.append(paste)
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
return recent_pastes
|
||||||
|
|
||||||
|
# Get paste by ID
|
||||||
|
def get_paste(unique_id, key=""):
|
||||||
|
file_path = common.determine_file(unique_id)
|
||||||
|
|
||||||
|
if file_path is not None:
|
||||||
|
with open(file_path, "r") as fp:
|
||||||
|
paste = json.loads(fp.read())
|
||||||
|
|
||||||
|
# Check if paste is expired
|
||||||
|
if common.is_expired(paste):
|
||||||
|
delete_paste(unique_id)
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Check remaining uses, and decrement
|
||||||
|
# -1 = unlimited uses
|
||||||
|
if paste['uses'] != -1:
|
||||||
|
paste['uses'] -= 1
|
||||||
|
if paste['uses'] == 0:
|
||||||
|
delete_paste(unique_id)
|
||||||
|
else:
|
||||||
|
with open(file_path, "w") as fp:
|
||||||
|
fp.write(json.dumps(paste))
|
||||||
|
|
||||||
|
# Decrypt content, if necessary
|
||||||
|
try:
|
||||||
|
if key != "":
|
||||||
|
cipher_suite = Fernet(key.encode('utf-8'))
|
||||||
|
paste['content'] = cipher_suite.decrypt(paste['content'].encode('utf-8')).decode('utf-8')
|
||||||
|
except Exception as e:
|
||||||
|
return 401
|
||||||
|
|
||||||
|
return paste
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Delete paste by ID
|
||||||
|
def delete_paste(unique_id):
|
||||||
|
paste = common.determine_file(unique_id)
|
||||||
|
if paste is not None:
|
||||||
|
remove(paste)
|
||||||
|
|
||||||
|
# Create new paste
|
||||||
|
def new_paste(title, content, source_ip, expires=0, single=False, encrypt=False):
|
||||||
|
unique_id = str(uuid.uuid4())
|
||||||
|
output_file = config.data_directory + "/" + unique_id
|
||||||
|
|
||||||
|
# Check for existing paste id (unlikely)
|
||||||
|
while path.exists(output_file) or path.exists(output_file + ".expires"):
|
||||||
|
unique_id = str(uuid.uuid4())
|
||||||
|
output_file = config.data_directory + "/" + unique_id
|
||||||
|
|
||||||
|
# Attempt to guess programming language
|
||||||
|
guesses = guess.probabilities(content)
|
||||||
|
language = guesses[0][0] if guesses[0][1] > config.guess_threshold and guesses[0][0] not in config.ignore_guess else "Plaintext"
|
||||||
|
|
||||||
|
# Check if encryption is necessary
|
||||||
|
key = ""
|
||||||
|
if encrypt:
|
||||||
|
init_key = Fernet.generate_key()
|
||||||
|
cipher_suite = Fernet(init_key)
|
||||||
|
content = cipher_suite.encrypt(content.encode('utf-8')).decode('utf-8')
|
||||||
|
key = init_key.decode('utf-8')
|
||||||
|
|
||||||
|
# Check if single use is set
|
||||||
|
uses = 2 if single else -1
|
||||||
|
|
||||||
|
# Check for expiration
|
||||||
|
now = datetime.now()
|
||||||
|
expiration = ""
|
||||||
|
if expires > 0:
|
||||||
|
expiration = (now + timedelta(hours=expires)).strftime("%a, %d %b %Y at %H:%M:%S")
|
||||||
|
output_file = output_file + ".expires"
|
||||||
|
|
||||||
|
output = {
|
||||||
|
"timestamp": now.strftime("%a, %d %b %Y at %H:%M:%S"),
|
||||||
|
"language": language,
|
||||||
|
"source_ip": source_ip,
|
||||||
|
"title": title,
|
||||||
|
"content": content,
|
||||||
|
"encrypted": encrypt,
|
||||||
|
"uses": uses,
|
||||||
|
"expiration": expiration
|
||||||
|
}
|
||||||
|
|
||||||
|
# Write to output file
|
||||||
|
with open(output_file, "w+") as fp:
|
||||||
|
fp.write(json.dumps(output))
|
||||||
|
|
||||||
|
return unique_id, key
|
||||||
|
|
||||||
|
# Purge expired pastes
|
||||||
|
def purge_expired_pastes():
|
||||||
|
print("Starting purge thread, with interval {0} seconds...".format(config.purge_interval))
|
||||||
|
while True:
|
||||||
|
for paste in Path(config.data_directory).iterdir():
|
||||||
|
if str(paste).endswith(".expires"):
|
||||||
|
unique_id = path.basename(paste)[:-8]
|
||||||
|
|
||||||
|
with open(paste, "r") as fp:
|
||||||
|
content = json.loads(fp.read())
|
||||||
|
|
||||||
|
# Check if paste is expired
|
||||||
|
if common.is_expired(content):
|
||||||
|
delete_paste(unique_id)
|
||||||
|
|
||||||
|
# Sleep for specified interval
|
||||||
|
time.sleep(config.purge_interval)
|
@ -0,0 +1,163 @@
|
|||||||
|
from __main__ import app, limiter, loaded_config
|
||||||
|
from . import config, common, functions
|
||||||
|
|
||||||
|
from flask import Flask, render_template, request, redirect, abort
|
||||||
|
from urllib.parse import quote
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
# Load themes
|
||||||
|
loaded_themes = common.get_themes()
|
||||||
|
|
||||||
|
########## Routes ##########
|
||||||
|
|
||||||
|
# Home page
|
||||||
|
@app.route("/")
|
||||||
|
def home():
|
||||||
|
whitelisted = common.verify_whitelist(common.get_source_ip(request))
|
||||||
|
pastes = []
|
||||||
|
|
||||||
|
if whitelisted or config.force_show_recent:
|
||||||
|
pastes = functions.get_recent()
|
||||||
|
|
||||||
|
return render_template("index.html",
|
||||||
|
pastes=pastes,
|
||||||
|
whitelisted=whitelisted,
|
||||||
|
active_theme=common.set_theme(request),
|
||||||
|
themes=loaded_themes,
|
||||||
|
force_show_recent=config.force_show_recent,
|
||||||
|
show_cli_button=config.show_cli_button,
|
||||||
|
script_url=request.url.rsplit('/', 1)[0] + "/pastey")
|
||||||
|
|
||||||
|
# New paste page
|
||||||
|
@app.route("/new")
|
||||||
|
def new():
|
||||||
|
whitelisted = common.verify_whitelist(common.get_source_ip(request))
|
||||||
|
return render_template("new.html",
|
||||||
|
whitelisted=whitelisted,
|
||||||
|
active_theme=common.set_theme(request),
|
||||||
|
themes=loaded_themes)
|
||||||
|
|
||||||
|
# Config page
|
||||||
|
@app.route("/config")
|
||||||
|
def config_page():
|
||||||
|
whitelisted = common.verify_whitelist(common.get_source_ip(request))
|
||||||
|
if not whitelisted:
|
||||||
|
abort(401)
|
||||||
|
|
||||||
|
return render_template("config.html",
|
||||||
|
config_items=loaded_config,
|
||||||
|
script_url=request.url.rsplit('/', 1)[0] + "/pastey",
|
||||||
|
whitelisted=whitelisted,
|
||||||
|
active_theme=common.set_theme(request),
|
||||||
|
themes=loaded_themes)
|
||||||
|
|
||||||
|
# View paste page
|
||||||
|
@app.route("/view/<unique_id>")
|
||||||
|
def view(unique_id):
|
||||||
|
content = functions.get_paste(unique_id)
|
||||||
|
|
||||||
|
if content is not None:
|
||||||
|
return render_template("view.html",
|
||||||
|
paste=content,
|
||||||
|
url=request.url,
|
||||||
|
whitelisted=common.verify_whitelist(common.get_source_ip(request)),
|
||||||
|
active_theme=common.set_theme(request),
|
||||||
|
themes=loaded_themes)
|
||||||
|
else:
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
# View paste page (encrypted)
|
||||||
|
@app.route("/view/<unique_id>/<key>")
|
||||||
|
def view_key(unique_id, key):
|
||||||
|
content = functions.get_paste(unique_id, key=key)
|
||||||
|
|
||||||
|
if content == 401:
|
||||||
|
abort(401)
|
||||||
|
elif content is not None:
|
||||||
|
return render_template("view.html",
|
||||||
|
paste=content,
|
||||||
|
url=request.url,
|
||||||
|
whitelisted=common.verify_whitelist(common.get_source_ip(request)),
|
||||||
|
active_theme=common.set_theme(request),
|
||||||
|
themes=loaded_themes)
|
||||||
|
else:
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
|
||||||
|
# Delete paste
|
||||||
|
@app.route("/delete/<unique_id>")
|
||||||
|
def delete(unique_id):
|
||||||
|
if not common.verify_whitelist(common.get_source_ip(request)):
|
||||||
|
abort(401)
|
||||||
|
|
||||||
|
functions.delete_paste(unique_id)
|
||||||
|
return redirect("/")
|
||||||
|
|
||||||
|
# Script download
|
||||||
|
@app.route("/pastey")
|
||||||
|
def pastey_script():
|
||||||
|
return render_template('pastey.sh', endpoint=request.url.rsplit('/', 1)[0] + "/raw"), 200, {
|
||||||
|
'Content-Disposition': 'attachment; filename="pastey"',
|
||||||
|
'Content-Type': 'text/plain'
|
||||||
|
}
|
||||||
|
|
||||||
|
# POST new paste
|
||||||
|
@app.route('/paste', methods = ['POST'])
|
||||||
|
@limiter.limit(config.rate_limit, exempt_when=lambda: common.verify_whitelist(common.get_source_ip(request)))
|
||||||
|
def paste():
|
||||||
|
source_ip = common.get_source_ip(request)
|
||||||
|
|
||||||
|
# Check if restrict pasting to whitelist CIDRs is enabled
|
||||||
|
if config.restrict_pasting and not common.verify_whitelist(source_ip):
|
||||||
|
abort(401)
|
||||||
|
|
||||||
|
content = request.form['content']
|
||||||
|
|
||||||
|
# Check if content is empty
|
||||||
|
if request.form['content'].strip() == "":
|
||||||
|
return redirect("/new")
|
||||||
|
else:
|
||||||
|
|
||||||
|
# Verify form options
|
||||||
|
title = request.form['title'] if request.form['title'].strip() != "" else "Untitled"
|
||||||
|
single = True if 'single' in request.form else False
|
||||||
|
encrypt = True if 'encrypt' in request.form else False
|
||||||
|
|
||||||
|
# Create paste
|
||||||
|
unique_id, key = functions.new_paste(title, content, source_ip, expires=int(request.form['expiration']), single=single, encrypt=encrypt)
|
||||||
|
if encrypt:
|
||||||
|
return redirect("/view/" + unique_id + "/" + quote(key))
|
||||||
|
else:
|
||||||
|
return redirect("/view/" + unique_id)
|
||||||
|
|
||||||
|
# POST new raw paste
|
||||||
|
@app.route('/raw', methods = ['POST'])
|
||||||
|
@limiter.limit(config.rate_limit, exempt_when=lambda: common.verify_whitelist(common.get_source_ip(request)))
|
||||||
|
def raw():
|
||||||
|
source_ip = common.get_source_ip(request)
|
||||||
|
|
||||||
|
# Check if restrict pasting to whitelist CIDRs is enabled
|
||||||
|
if config.restrict_raw_pasting and not common.verify_whitelist(source_ip):
|
||||||
|
abort(401)
|
||||||
|
|
||||||
|
# Create paste
|
||||||
|
unique_id, key = new_paste("Untitled", request.data.decode('utf-8'), source_ip, single=False, encrypt=False)
|
||||||
|
link = request.url.rsplit('/', 1)[0] + "/view/" + unique_id
|
||||||
|
|
||||||
|
return link, 200
|
||||||
|
|
||||||
|
# Custom 404 handler
|
||||||
|
@app.errorhandler(404)
|
||||||
|
def page_not_found(e):
|
||||||
|
return render_template('404.html',
|
||||||
|
whitelisted=common.verify_whitelist(common.get_source_ip(request)),
|
||||||
|
active_theme=common.set_theme(request),
|
||||||
|
themes=loaded_themes), 404
|
||||||
|
|
||||||
|
# Custom 401 handler
|
||||||
|
@app.errorhandler(401)
|
||||||
|
def unauthorized(e):
|
||||||
|
return render_template('401.html',
|
||||||
|
whitelisted=common.verify_whitelist(common.get_source_ip(request)),
|
||||||
|
active_theme=common.set_theme(request),
|
||||||
|
themes=loaded_themes), 401
|
Before Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 120 KiB |
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 125 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 123 KiB After Width: | Height: | Size: 129 KiB |
After Width: | Height: | Size: 21 KiB |
@ -0,0 +1,105 @@
|
|||||||
|
body, tr {
|
||||||
|
background-color: #262626 !important;
|
||||||
|
color: rgba(255, 255, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pastey-logo {
|
||||||
|
background-image: url('/static/img/pastey-dark.png');
|
||||||
|
}
|
||||||
|
|
||||||
|
.pastey-navbar {
|
||||||
|
background-color: #262626 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pastey-link {
|
||||||
|
color: rgba(255, 255, 255, 0.80);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pastey-link:hover {
|
||||||
|
color: rgba(255, 255, 255, 0.65);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pastey-header {
|
||||||
|
background-color: #2f2f2f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pastey-input {
|
||||||
|
color: rgba(255, 255, 255) !important;
|
||||||
|
background-color: #2f2f2f !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pastey-input-title {
|
||||||
|
color: rgba(255, 255, 255) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pastey-preview-image {
|
||||||
|
background-color: #2f2f2f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pastey-select {
|
||||||
|
background-color: #2f2f2f;
|
||||||
|
color: rgba(255, 255, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nocode {
|
||||||
|
color: rgb(255,255,255) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Google prettify elements */
|
||||||
|
|
||||||
|
/* Alternating line colors */
|
||||||
|
li.L1,li.L3,li.L5,li.L7,li.L9 {
|
||||||
|
background:#2a2a2a;
|
||||||
|
}
|
||||||
|
|
||||||
|
li.L0,li.L2,li.L4,li.L6,li.L8 {
|
||||||
|
background:#222222;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Syntax highlighting
|
||||||
|
See Google prettify for more details
|
||||||
|
*/
|
||||||
|
.pln, .pun {
|
||||||
|
color: rgba(255, 255, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
.kwd {
|
||||||
|
color: rgb(152, 209, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
.com {
|
||||||
|
color: rgb(255, 51, 51);
|
||||||
|
}
|
||||||
|
|
||||||
|
.str {
|
||||||
|
color: rgb(0, 195, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.lit {
|
||||||
|
color: rgb(0, 161, 161);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag {
|
||||||
|
color: rgb(0, 167, 218);
|
||||||
|
}
|
||||||
|
|
||||||
|
.atn {
|
||||||
|
color: rgb(248, 1, 248);
|
||||||
|
}
|
||||||
|
|
||||||
|
.atv {
|
||||||
|
color: rgb(0, 195, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dec, .var {
|
||||||
|
color: rgb(255, 2, 129);
|
||||||
|
}
|
||||||
|
|
||||||
|
.typ {
|
||||||
|
color: rgb(197, 0, 197);
|
||||||
|
}
|
||||||
|
|
||||||
|
.opn, .clo {
|
||||||
|
color: rgb(218, 218, 0);
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
.pastey-logo {
|
||||||
|
background-image: url('/static/img/pastey.png');
|
||||||
|
}
|
||||||
|
|
||||||
|
.pastey-link {
|
||||||
|
color: rgba(0, 0, 0, 0.55);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pastey-link:hover {
|
||||||
|
color: rgba(0, 0, 0, 0.70);;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Syntax highlighting
|
||||||
|
See Google prettify for more details
|
||||||
|
*/
|
||||||
|
.pun {
|
||||||
|
color: rgba(0, 0, 0);
|
||||||
|
}
|