Redesign 🚀

This commit is contained in:
Martin Mahner 2018-03-24 08:57:05 +01:00
parent 54157aeda5
commit ace0704f45
36 changed files with 2285 additions and 664 deletions

100
client/js/dpaste.js Normal file
View file

@ -0,0 +1,100 @@
/*jshint strict:false */
if (typeof console === "undefined" || typeof console.log === "undefined") {
console = {};
console.log = function () { };
}
// -----------------------------------------------------------------------------
// Add data-platform to the body tag to show platform related shortcuts
// -----------------------------------------------------------------------------
const isWindows = navigator.appVersion.indexOf("Win") !== -1;
document.body.dataset.platform = isWindows ? 'win' : 'mac';
// -----------------------------------------------------------------------------
// Autofocus the content field on the homepage
// -----------------------------------------------------------------------------
const af = document.querySelector(".autofocus textarea");
if (af !== null) {
af.focus();
}
// -----------------------------------------------------------------------------
// Cmd+Enter or Ctrl+Enter submits the form
// -----------------------------------------------------------------------------
document.body.onkeydown = function(e) {
const metaKey = isWindows ? e.ctrlKey : e.metaKey;
const form = document.querySelector(".snippet-form");
if (form && e.keyCode === 13 && metaKey) {
form.submit();
return false;
}
};
// -----------------------------------------------------------------------------
// Toggle Wordwrap
// -----------------------------------------------------------------------------
const wordwrapCheckbox = document.getElementById('wordwrap');
const snippetDiv = document.querySelector('.snippet-code');
function toggleWordwrap() {
if (wordwrapCheckbox.checked) {
snippetDiv.classList.add('wordwrap');
} else {
snippetDiv.classList.remove('wordwrap');
}
}
if (wordwrapCheckbox && snippetDiv) {
toggleWordwrap();
wordwrapCheckbox.onchange = toggleWordwrap;
}
// -----------------------------------------------------------------------------
// Line Highlighting
// -----------------------------------------------------------------------------
const curLine = document.location.hash;
if (curLine.startsWith('#L')) {
const hashlist = curLine.substring(2).split(',');
if (hashlist.length > 0 && hashlist[0] !== '') {
hashlist.forEach(function(el) {
const line = document.getElementById(`l${el}`);
if (line) {
line.classList.add('marked');
}
});
}
}
let lines = document.querySelectorAll('.snippet-code li');
lines.forEach(function(el) {
el.onclick = function() {
el.classList.toggle('marked');
let hash = 'L';
let marked = document.querySelectorAll('.snippet-code li.marked');
marked.forEach(function(line) {
if (hash !== 'L') {
hash += ',';
}
hash += line.getAttribute('id').substring(1);
});
window.location.hash = hash;
};
});
// -----------------------------------------------------------------------------
// Copy URL to Clipboard
// -----------------------------------------------------------------------------
const clipboardLink = document.getElementById('copyToClipboard');
const copyToClipboardField = document.getElementById('copyToClipboardField');
if (clipboardLink && copyToClipboardField) {
clipboardLink.onclick = function(e) {
e.preventDefault();
copyToClipboardField.select();
document.execCommand("Copy");
console.log('Copied URL to clipboard:', copyToClipboardField.value);
};
}

47
client/scss/_globals.scss Normal file
View file

@ -0,0 +1,47 @@
body {
min-width: 800px; // FIXME: MOBILE
background-color: $bgColor;
font-family: $baseFont;
font-weight: $baseFontRegular;
}
body[data-code-snippet] { background-color: $codeBgColor; }
body[data-platform=win] .platform-mac { display: none; }
body[data-platform=mac] .platform-win { display: none; }
.btn {
padding: 6px 0;
position: relative;
display: inline-block;
color: $btnTextColor;
background-color: $btnBgColor;
border: 1px solid $btnBorderColor;
border-radius: 3px;
font-size: 13px;
font-weight: $baseFontDemiBold;
text-decoration: none;
text-align: center;
cursor: pointer;
.sep {
@include separator();
}
&:hover {
text-decoration: none;
background-color: darken($btnBgColor, 5%);
}
&:active {
top: 1px;
}
}

7
client/scss/_mixins.scss Normal file
View file

@ -0,0 +1,7 @@
// Vertical dotted separtor
@mixin separator($color: white, $margin: 4px) {
width: 0;
border-right: 2px dotted $color;
margin: 0 ($margin+2px) 0 $margin;
}

40
client/scss/_reset.scss Normal file
View file

@ -0,0 +1,40 @@
/* http://meyerweb.com/eric/tools/css/reset/
v2.0 | 20110126
License: none (public domain)
*/
html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline; }
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section {
display: block; }
body {
line-height: 1; }
ol, ul {
list-style: none; }
blockquote, q {
quotes: none; }
blockquote {
&:before, &:after {
content: '';
content: none; } }
q {
&:before, &:after {
content: '';
content: none; } }
table {
border-collapse: collapse;
border-spacing: 0; }

View file

@ -0,0 +1,42 @@
/*
Generic Content pages such as about or 404 error pages.
*/
article {
padding: 40px $boxPadding;
font-size: 16px;
font-weight: $baseFontRegular;
line-height: 24px;
max-width: 520px;
color: $textColor;
h2 {
font-size: 28px;
line-height: 36px;
margin: 0 0 30px 0;
}
p {
margin: 30px 0;
}
strong, b {
font-weight: $baseFontDemiBold;
}
a:link, a:visited {
color: $linkColor;
text-decoration: underline;
text-decoration-color: lighten($linkColor, 30%);
}
a:hover, a:active {
color: $hoverColor;
text-decoration: underline;
}
}

View file

@ -1,8 +1,49 @@
// -----------------------------------------------------------------------------
// Pygments Theme
.snippet-code {
font-family: $codeFont;
font-size: 13px;
font-weight: 300;
line-height: 20px;
//overflow: auto;
color: $codeTextColor;
background-color: $codeBgColor;
padding: 20px $boxPadding;
&.wordwrap {
overflow: auto;
li { white-space: pre-wrap !important; }
}
ol {
position: relative;
list-style: none;
counter-reset: lineNumberCounter;
li {
white-space: pre;
padding-left: 50px;
&:before {
color: $codeLineNumberColor;
counter-increment: lineNumberCounter;
content: counter(lineNumberCounter);
text-align: right;
width: 30px;
position: absolute;
display: inline-block;
left: 0px;
}
}
li.marked {
background-color: $markerBgColor;
&:before{ color: $markerLineNumberColor; }
}
}
// Pygments
.gd { background-color: rgba(226, 12, 19, .3); color: #fff; display: block; }
.gi { background-color: rgba(23, 189, 10, .2); color: #fff; display: block; }
@ -67,3 +108,20 @@
.il { color: #ae81ff } /* Literal.Number.Integer.Long */
}
.snippet-text {
background: $bgColor;
padding: 20px $boxPadding;
color: $textColor;
font-family: $textFont;
font-weight: $baseFontRegular;
font-size: 16px;
line-height: 22px;
& > div {
max-width: 540px;
}
}

View file

@ -0,0 +1,86 @@
@mixin codeTextArea {
color: #7D7D7D;
font-family: $codeFont;
font-size: 12px;
line-height: 17px;
}
.snippet-form {
background-color: $bgColor;
select {
-moz-appearance: none;
-webkit-appearance: none;
padding: 5px 7px;
margin-right: 15px;
min-width: 160px;
color: $selectTextColor;
background-color: $selectBgColor;
border: 1px solid $selectBorderColor;
border-radius: 3px;
font-family: $baseFont;
font-weight: $baseFontRegular;
font-size: 14px;
cursor: pointer;
// Triangle
background-image:
linear-gradient(45deg, transparent 50%, $selectTriangleColor 50%),
linear-gradient(135deg, $selectTriangleColor 50%, transparent 50%);
background-position:
calc(100% - 18px) calc(13px),
calc(100% - 13px) calc(13px),
calc(100% - 2.5em) 0.5em;
background-size: 5px 5px, 5px 5px, 3px 1.5em;
background-repeat: no-repeat;
&:hover {
border-color: darken($selectBorderColor, 10%);
}
}
.options {
padding: 0 $boxPadding;
height: 60px;
display: flex;
align-items: center;
border-bottom: 1px solid $borderColor;
label { display: none; }
.action {
margin-left: auto;
.btn {
width: auto;
padding: 6px 20px;
}
}
}
.content {
padding: 20px $boxPadding;
label { display: none; }
textarea {
padding: 20px;
@include codeTextArea;
min-height: 390px;
box-sizing: border-box;
width: 100%;
border: 1px solid $borderColor;
&:active, &:focus {
border-color: darken($selectBorderColor, 10%)
}
}
}
}

View file

@ -0,0 +1,115 @@
header {
padding: 0 $boxPadding;
height: 60px;
display: flex;
justify-content: space-between;
align-items: center;
color: $headerTextColor;
background: linear-gradient(to right, $headerBgColor1, $headerBgColor2);
// Subheadline e.g. Reply Bar
&.sub {
height: 45px;
}
a {
display: inline-block;
color: $headerTextColor;
text-decoration: none;
text-align: center;
font-weight: $baseFontBold;
&:hover { text-decoration: underline; }
// dpaste home link is a bit larger
&.home {
font-size: 28px;
font-weight: $baseFontHeavy;
}
// Nav links have a fixed width to align with grid
&.nav-link {
width: $columnWidth + $columnGap;
}
}
.btn {
width: 2 * $columnWidth;
margin-left: $columnGap / 2;
}
h1 {
position: relative;
font-size: 24px;
font-weight: $baseFontBold;
strong {
font-size: 28px;
font-weight: $baseFontHeavy;
}
}
h2 {
font-size: 16px;
font-weight: $baseFontBold;
}
}
ul#snippetOptions {
padding: 0 $boxPadding;
height: 40px;
background-color: $metaBgColor;
color: $metaTextColor;
font-size: 13px;
font-weight: $baseFontRegular;
display: flex;
align-items: center;
a:link, a:visited {
color: $metaTextColor;
text-decoration: underline;
}
a:hover, a:active {
color: $hoverColor;
text-decoration: underline;
}
li {
padding: 0 7px;
&:first-child{ padding-left: 0; }
&:last-child{ padding-right: 0; }
}
li.sep {
@include separator($metaTextColor);
height: 17px;
margin: 0 2px 0 1px;
padding: 0;
}
}
#copyToClipboardField {
position: absolute;
left: -9999px;
}
#copyToClipboard svg {
height: 30px;
position: absolute;
top: -8px;
right: -40px;
&:active {
top: -7px;
}
}

View file

@ -0,0 +1,33 @@
.history-header {
padding: 15px $boxPadding;
color: $textColor;
font-size: 14px;
background-color: $borderColor;
a:link, a:visited {
font-weight: $baseFontDemiBold;
color: $textColor;
text-decoration: underline;
}
a:hover, a:active {
color: $hoverColor;
text-decoration: underline;
}
.sep {
@include separator($metaTextColor, 10px);
height: 17px;
padding: 0;
}
}
.history-empty {
padding: 40px $boxPadding;
color: $codeTextColor;
}

93
client/scss/dpaste.scss Executable file
View file

@ -0,0 +1,93 @@
/* -----------------------------------------------------------------------------
Palette
----------------------------------------------------------------------------- */
// General
$bgColor: white; // Regular background color for non-code snippets.
$textColor: #7D7D7D; // General text color
$linkColor: #4A90E2; // Link color in text and content pages
$hoverColor: #72B4E4;
$borderColor: #EDEDED; // Used for separators, select borders, etc.
// Header
$headerTextColor: white; // Header text color
$headerBgColor1: #4A90E2; // Header gradient background left
$headerBgColor2: #72B4E4; // Header gradient background right
$btnBgColor: #4A90E2; // Buttons (Header, Meta)
$btnBorderColor: #33639C;
$btnTextColor: white;
// Meta Options
$metaBgColor: #F9F9F9; // Meta options of snippet
$metaTextColor: #BABABA; // Meta text and link color
$selectBorderColor: #DDDDDD; // Select dropdowns
$selectBgColor: white;
$selectTextColor: #858585;
$selectTriangleColor: $linkColor;
$confirmTextColor: #6D6D6D; // Yellow confirmation popup
$confirmBgColor: #FFF9A8;
// Snippet
$messageTextColor: white; // One time message color
$messageBgColor: #F5A623;
$codeTextColor: #dadad4; // Regular code color (not specified by pygments)
$codeBgColor: #222829; // bg color of code views
$codeDiffBgColor: #2A3335; // bg color of diff views
$codeLineNumberColor: #636363; // Color of code line numbers
$markerLineNumberColor: gold; // Marked lines number color
$markerBgColor: rgba(255, 255, 255, .05);
// Mobile Burger Menu
$burgerBgColor: #3D3D3D; // Burger Menu background
$burgerTextColor: #C0C0C0; // (muted) Text color of burger buttons
$burgerBtnTextColor: #C0C0C0; // Buttons and select items in Burger Menu
$burgerBtnBorderColor: #575757;
/* -----------------------------------------------------------------------------
Fonts
----------------------------------------------------------------------------- */
$baseFont: "Avenir Next";
$codeFont: "SF Mono", "Fira Mono", Monaco, Menlo, Consolas, monospace;
$textFont: "Apple SD Gothic Neo";
$baseFontLight: 300;
$baseFontRegular: 400;
$baseFontDemiBold: 500;
$baseFontBold: 600;
$baseFontHeavy: 700;
/* -----------------------------------------------------------------------------
Grid
----------------------------------------------------------------------------- */
$columnWidth: 60px;
$columnGap: 20px;
$boxPadding: 30px; // Left/Right padding of all views
$mobileBoxPadding: 20px;
/* -----------------------------------------------------------------------------
Imports
----------------------------------------------------------------------------- */
// Globals.
@import 'reset';
@import 'mixins';
@import 'globals';
// Components.
@import 'components/header';
@import 'components/form';
@import 'components/code';
@import 'components/article';
@import 'components/history';

View file

@ -1,13 +0,0 @@
// -----------------------------------------------------------------------------
// About Page
.page {
padding: $desktopBoxPadding;
background-color: white;
color: #444;
table th {
text-align: left;
}
}

View file

@ -1,97 +0,0 @@
// -----------------------------------------------------------------------------
// Snippet Details
// -----------------------------------------------------------------------------
// Options
.snippet-options {
padding: $desktopBoxPadding;
font-size: .9rem;
a:link,
a:visited {
margin: 0 .2em;
}
a:hover,
a:active {
text-decoration: underline;
}
}
// -----------------------------------------------------------------------------
// Reply Form
.snippet-reply {
h2 {
background-color: white;
padding: $desktopBoxPadding;
padding-bottom: 0;
margin: 0;
font-size: 1.5rem;
font-weight: 500;
}
}
.snippet-reply[data-hidden=yes] {
opacity: 0.3;
cursor: pointer;
* {
cursor: pointer;
}
}
// -----------------------------------------------------------------------------
// Text lexer
.snippet-text {
color: #666;
font-size: 16px;
line-height: 24px;
max-width: 620px;
font-family: Helvetica, FreeSerif, serif;
font-weight: 300;
}
// -----------------------------------------------------------------------------
// Code Lexer
$codeColor: #f8f8f2;
$codeLineColor: #aaa;
$codeBackgroundColor: #232829;
$markerColor: rgb(244,244,9);
$markerBackgroundColor: rgba(244,244,9,.2);
.snippet-code {
background: $codeBackgroundColor;
color: $codeColor;
font-family: $codeFont;
font-size: 0.8rem;
font-weight: 300;
line-height: 1.1rem;
padding: 1rem 0;
&.wordwrap {
overflow: auto;
li { white-space: pre-wrap !important; }
}
ol {
margin: 0 0 0 2rem;
li {
color: $codeLineColor;
cursor: pointer;
white-space: pre;
&.marked {
color: $markerColor;
background-color: $markerBackgroundColor;
}
}
}
}

View file

@ -1,74 +0,0 @@
// -----------------------------------------------------------------------------
// Snippet Form
.snippet-form {
}
// -----------------------------------------------------------------------------
// The main form textarea
.form-content {
background-color: white;
padding: 1rem 1rem;
textarea {
box-sizing: border-box;
width: 100%;
height: 25rem;
font-family: $codeFont;
font-size: .8rem;
border: 1px solid #bbb;
padding: 1rem;
transition: border .5s linear;
&:focus {
outline: none;
border-color: #3fca6e;
}
}
}
// -----------------------------------------------------------------------------
// Additional options; lexer and expiration
.form-options {
padding: $desktopBoxPadding;
display: flex;
justify-content: space-between;
font-size: .9rem;
.form-options-lexer {
}
.form-options-expire {
}
}
// -----------------------------------------------------------------------------
// Submit line
.form-actions {
padding: $desktopBoxPadding;
background-color: white;
.shortcut {
font-size: .9rem;
color: #888;
letter-spacing: 1px;
}
input[type=submit] {
color: white;
background-color: rgb(109, 163, 217);
border: none;
padding: .6em 1em;
font-weight: 500;
font-family: "SF Mono", "Fira Mono", monospace;
border-radius: 2px;
cursor: pointer;
&:hover {
background: rgb(96, 139, 192);
}
}
}

View file

@ -1,61 +0,0 @@
// -----------------------------------------------------------------------------
// Header
header {
background: $headerColor2;
display: flex;
justify-content: space-between;
align-items: center;
padding: $desktopBoxPadding;
}
header h1 {
color: white;
font-size: 1em;
font-weight: 500;
margin: 0;
}
header h1 input {
font-size: 1em;
font-family: $codeFont;
background-color: transparent;
border: none;
padding: 0;
margin: 0;
color: white;
min-width: 25em;
outline: none;
}
header nav {
font-size: .9rem;
}
header nav a:link,
header nav a:visited {
color: white;
font-weight: 500;
text-decoration: none;
margin: 0 .2em;
white-space: nowrap;
&:hover {
text-decoration: underline;
}
}
header nav a:last-child {
border: 1px solid white;
border-radius: 5px;
padding: .3em .5em;
&:hover {
text-decoration: none;
background-color: lighten($headerColor1, 10%);
}
}

View file

@ -1,18 +0,0 @@
// -----------------------------------------------------------------------------
// Snippet History List
.snippet-list {
padding: $desktopBoxPadding;
dt:not(:first-child) {
margin-top: 1rem;
}
dt:last-child {
margin-top: 2rem;
}
dd {
font-family: $codeFont;
font-size: .9rem;
}
}

File diff suppressed because one or more lines are too long

View file

@ -1,67 +0,0 @@
// -----------------------------------------------------------------------------
//
// dpaste example stylesheet
//
// -----------------------------------------------------------------------------
// Color Palette
$backgroundColor: #F7F8F9;
$headerColor1: #394c78;
$headerColor2: #426ca1;
$headerTextColor: #fff;
$textColor: #888;
$linkColor: rgb(109, 163, 217);
$hoverColor: rgb(109, 163, 217);
// The general font for the website
$siteFont: "SF Mono", "Fira Mono", monospace;
// Font used for "Text" lexer
$textFont: Helvetica;
// Font used for the code snippet display
$codeFont: "SF Mono", "Fira Mono", Monaco, Menlo, Consolas, "Courier New", monospace;
$desktopBoxPadding: 1rem 2rem;
$mobileBoxPadding: 1rem;
// -----------------------------------------------------------------------------
body {
color: $textColor;
background: $backgroundColor;
font-family: $siteFont;
font-size: 1em;
font-weight: 300;
margin: 0;
padding: 0;
}
a:link,
a:visited {
color: $linkColor;
font-family: $siteFont;
font-weight: 500;
text-decoration: none;
}
a:hover,
a:visited {
color: $hoverColor;
text-decoration: underline;
}
select, input {
font-size: medium;
}
// -----------------------------------------------------------------------------
// Imports
@import 'header';
@import 'form';
@import 'details';
@import 'history';
@import 'about';
@import 'pygments';

View file

@ -1,8 +1,14 @@
{% extends "dpaste/base.html" %}
{% block title %}404 Not found{% endblock %}
{% block headline %}404 Not found{% endblock %}
{% block title %}404 Snippet not found{% endblock %}
{% block page %}
<p>Snippet you have searched is not available (anymore).</p>
<article>
<h2>404 Snippet not found</h2>
<p>
This snippet no longer exists. Snippets are automatically
deleted when they reach their expiration date.
</p>
</article>
{% endblock %}

View file

@ -1,8 +1,10 @@
{% extends "dpaste/base.html" %}
{% block title %}500 Internal Server Error{% endblock %}
{% block headline %}500 Internal Server Error{% endblock %}
{% block page %}
<p>Sorry, there was an error with your request. The server notified the admin via email.</p>
<article>
<h2>500 Internal Server Error</h2>
<p>There was an issue with your request.</p>
</article>
{% endblock %}

View file

@ -3,91 +3,28 @@
{% load i18n %}
{% block title %}{% trans "About" %}{% endblock %}
{% block headline %}{% trans "About" %}{% endblock %}
{% block page %}
<article>
<div class="page">
<h2>{% trans "About dpaste" %}</h2>
<p>
This site is powered by dpaste, which is open source. You can
find the source, contribute to it and leave ideas on Github:
<a href="https://github.com/bartTC/dpaste">github.com/bartTC/dpaste</a>
{% blocktrans %}
This site is powered by dpaste, an <strong>open source</strong>
<a href="https://en.wikipedia.org/wiki/Pastebin">pastebin</a> application.
You can find the source code, contribute to it or leave feedback
<a href="https://github.com/bartTC/dpaste">on Github</a>.
{% endblocktrans %}
</p>
<form action="/i18n/setlang/" method="post">
Change the language:
{% csrf_token %}
<input name="next" type="hidden" value="{{ redirect_to }}" />
<select name="language">
{% get_language_info_list for LANGUAGES as languages %}
{% for language in languages %}
<option value="{{ language.code }}"{% if language.code == LANGUAGE_CODE %} selected="selected"{% endif %}>
{{ language.name_local }} ({{ language.code }})
</option>
{% endfor %}
</select>
<input type="submit" value="Go" />
</form>
<h3>API</h3>
<p>dpaste provides a simple API documented in detail <a href="http://dpaste.readthedocs.org/en/latest/api.html">
on this page</a>. For a quick start here is a code example (Python 2.x):</p>
{# Just put the script in dpaste and copy the source node #}
<div class="code python"><ol><li id="1"><span class="c">#!/usr/bin/env python</span></li><li id="2">&nbsp;</li><li id="3"><span class="kn">from</span> <span class="nn">urllib</span> <span class="kn">import</span> <span class="n">urlencode</span></li><li id="4"><span class="kn">from</span> <span class="nn">urllib2</span> <span class="kn">import</span> <span class="n">Request</span><span class="p">,</span> <span class="n">urlopen</span></li><li id="5"><span class="kn">from</span> <span class="nn">sys</span> <span class="kn">import</span> <span class="n">stdin</span></li><li id="6">&nbsp;</li><li id="7"><span class="k">def</span> <span class="nf">paste_code</span><span class="p">():</span></li><li id="8">&nbsp;&nbsp;&nbsp;&nbsp;<span class="n">request</span> <span class="o">=</span> <span class="n">Request</span><span class="p">(</span><span class="s">'{{ site_url }}/api/'</span><span class="p">,</span> <span class="n">urlencode</span><span class="p">({</span></li><li id="9">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="s">'content'</span><span class="p">:</span> <span class="n">stdin</span><span class="o">.</span><span class="n">read</span><span class="p">(),</span></li><li id="10">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="s">'lexer'</span><span class="p">:</span> <span class="s">'python'</span><span class="p">,</span></li><li id="11">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="s">'format'</span><span class="p">:</span> <span class="s">'url'</span><span class="p">,</span></li><li id="12">&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">}))</span></li><li id="13">&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">print</span> <span class="n">urlopen</span><span class="p">(</span><span class="n">request</span><span class="p">)</span><span class="o">.</span><span class="n">read</span><span class="p">()</span></li><li id="14">&nbsp;</li><li id="15"><span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">'__main__'</span><span class="p">:</span></li><li id="16">&nbsp;&nbsp;&nbsp;&nbsp;<span class="n">paste_code</span><span class="p">()</span></li></ol></div>
<br/>
<p>Save this script in <code>/usr/local/bin/dpaste</code> and give it the executable bit: <code>chmod +x /usr/local/bin/dpaste</code>.</p>
<p>Usage: <code>cat foo.txt | dpaste</code></p>
<p>An alternative would be to just use <code>curl</code>:
<code>alias dpaste="curl -F 'content=&lt;-' {{ site_url }}/api/"</code></p>
<h3>Applications using the API:</h3>
<ul>
<li><a href="https://github.com/bartTC/dpasteGUI/wiki">dpasteGUI</a>, a OS X interface</li>
<li><a href="https://github.com/bartTC/SubDpaste">a dpaste Sublime 2 plugin</a></li>
<li><a href="http://marmalade-repo.org/packages/dpaste_de">Marmalade</a>, a Emacs plugin</li>
<li><a href="https://atom.io/packages/atom-dpaste">atom-dpaste</a>, a Atom editor plugin</li>
</ul>
<h3>{% trans "Statistics" %}</h3>
<p>{% blocktrans %}There are {{ total }} snippets in the database. The most popular languages are:{% endblocktrans %}</p>
<table class="table" style="width: 40%">
{% for s in stats %}
<tr>
<th>{{ s.lexer|upper }}</th>
<td>{{ s.count }}</td>
</tr>
{% endfor %}
</table>
<h3>Delete a snippet</h3>
<p>
If you created a snippet with the API you can't delete it on the webpage
since it's not in your history. You can delete a snippet here. Actually
you can delete any snippet of anybody, as long as you know the short code.
</p>
<p>
If you deleted a snippet because of legal issues, please let me know
that, I want to keep track of such things and try to avoid in future.
</p>
<p>
Type the 4 letter code of your snippet in the field and submit.
Like this yellow one here: <tt>{{ site_url }}/<strong>SiZrT</strong></tt>
{% blocktrans %}
dpaste provides an <strong>API</strong> to easily paste snippets out of your shell or editor.
See the <a href="https://github.com/bartTC/dpaste/wiki">dpaste wiki</a>
for the API documentation and a list of available plugins.
{% endblocktrans %}
</p>
<form method="POST" action="{% url "snippet_delete" %}">
{% csrf_token %}
<input name="snippet_id"> <input type="Submit" value="Submit"/>
</form>
{{ DPASTE_ABOUT_EXTRA }}
</div>
</article>
{% endblock %}

View file

@ -1,48 +1,29 @@
{% load i18n %}
{% load staticfiles %}
{% load dpaste_tags %}
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}{% endblock %}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="{% static "dpaste/dpaste.css" %}"/>
{% block extrahead %}{% endblock %}
<style type="text/css">{% inlinestatic "dpaste.css" %}</style>
</head>
<body>
<body {% block body_type %}{%endblock %}>
<header class="box">
<h1>{% block headline %}{% endblock %}</h1>
<header>
<h1>{% block headline %}<a class="home" href="{% url "snippet_new" %}">dpaste</a>{% endblock %}</h1>
<nav>
<a href="{% url "dpaste_about" %}">{% trans "About" %}</a>
<a href="{% url "snippet_history" %}">{% trans "History" %}</a>
<a href="{% url "snippet_new" %}">{% trans "New snippet" %} &rarr;</a></li>
<a class="nav-link" href="{% url "dpaste_about" %}">{% trans "About" %}</a>
<a class="nav-link" href="{% url "snippet_history" %}">{% trans "History" %}</a>
{%block nav_new %}<a class="btn" href="{% url "snippet_new" %}">{% trans "New snippet" %}</a>{% endblock %}
</nav>
</header>
{% block page %}
PAGE MISSING
{% endblock %}
{% block options %}{% endblock %}
{% block script_footer %}
<script>
let af = document.querySelector(".autofocus textarea");
if (af !== null)
af.focus();
let se = document.querySelector(".superenter textarea");
if (se !== null) {
se.onkeydown = function (e) {
let metaKey = navigator.appVersion.indexOf("Win") !== -1 ? e.ctrlKey : e.metaKey;
if (e.keyCode === 13 && metaKey) {
document.querySelector(".snippet-form").submit();
return false;
}
}
}
</script>
{% endblock %}
<main>{% block page %}{% endblock %}</main>
<script>{% inlinestatic "dpaste.js" %}</script>
</body>
</html>

View file

@ -0,0 +1,83 @@
{% extends "dpaste/base.html" %}
{% load i18n %}
{% block title %}dpaste/{{ snippet.secret_id }} ({{ snippet.lexer }}){% endblock %}
{% block body_type %}
{% if snippet.lexer == 'text' %}data-text-snippet{% else %}data-code-snippet{% endif %}
{% endblock %}
{% block headline %}
<a href="{{ request.build_absolute_uri }}">{{ request.build_absolute_uri }}</a>
<a href="#" id="copyToClipboard" title="{% trans "Copy URL to clipboard" %}">
<svg fill="#FFF" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<path d="M40.965 6c5.137 0 9.273 3.988 9.8 9h1.188c3.14 0 5.809 2.125 6.68 5h2.309c4.39 0 7.988 3.766 7.988 8.188V55h15.98-.004a1.99 1.99 0 0 1 1.574.652c.356.39.543.91.516 1.441A1.995 1.995 0 0 1 84.906 59h-15.98v26.812c0 4.422-3.598 8.188-7.988 8.188h-39.95C16.598 94 13 90.234 13 85.812V28.186C13 23.765 16.598 20 20.99 20h2.308c.871-2.875 3.54-5 6.68-5h1.187c.528-5.012 4.66-9 9.801-9zm0 4a5.968 5.968 0 0 0-5.992 6v1a2 2 0 0 1-2 2h-2.996c-1.696 0-2.996 1.305-2.996 3s1.3 3 2.996 3H51.95c1.695 0 2.996-1.305 2.996-3s-1.301-3-2.996-3h-2.996a1.998 1.998 0 0 1-1.996-2v-1c0-3.336-2.66-6-5.993-6zm19.973 14h-2.309c-.87 2.875-3.539 5-6.68 5H29.978c-3.14 0-5.809-2.125-6.68-5h-2.309c-1.968 0-3.996 2.05-3.996 4.188v57.624c0 2.137 2.024 4.188 3.996 4.188h39.95c1.968 0 3.996-2.05 3.996-4.188v-26.81H46.176l6.117 5.5c.828.742.894 2.015.156 2.843a2.01 2.01 0 0 1-2.84.157l-9.988-9a2 2 0 0 1 0-3l9.988-9a2.005 2.005 0 0 1 1.465-.532 2.002 2.002 0 0 1 1.219 3.532L46.175 55h18.758V28.189c0-2.137-2.027-4.188-3.996-4.188z"/>
</svg>
</a>
<input type="text" id="copyToClipboardField" value="{{ request.build_absolute_uri }}"/>
{% endblock %}
{% block options %}
<ul id="snippetOptions">
<li>
{% if snippet.expire_type == 1 %}
{% blocktrans with date=snippet.expires|timeuntil %}Expires in: {{ date }}{% endblocktrans %}
{% elif snippet.expire_type == 2 %}
{% trans "Snippet never expires" %}
{% elif snippet.expire_type == 3 %}
{% trans "One-Time snippet" %}
{% endif %}
</li>
<li class="sep"></li>
<li>
<a href="{% url "snippet_delete" snippet.secret_id %}"
onclick="return confirm('{% trans "Are you sure you want to delete this snippet?" %}');">{% trans "Delete Now" %}</a>
</li>
<!--
{% if snippet.parent %}
<li><a href="#snippet-diff">{% trans "Compare with previous Snippet" %}</a></li>
{% endif %}
-->
{% if snippet.expire_type != 3 %}
<li><a href="{% url "snippet_details_raw" snippet.secret_id %}">{% trans "View Raw" %}</a></li>
{% endif %}
{% if snippet.lexer != 'text' %}
<li>
<label for="wordwrap">
<input type="checkbox" id="wordwrap"{% if wordwrap %} checked{% endif %}> Wordwrap
</label>
</li>
{% endif %}
</ul>
{% endblock %}
{% block page %}
{% if snippet.expire_type == 3 %}
<p class="snippet-message">
{% trans "This is a one-time snippet." %}
{% if snippet.remaining_views > 1 %}
{% blocktrans with remaining=snippet.remaining_views %}It is automatically removed after {{ remaining }} further views.{% endblocktrans %}
{% elif snippet.remaining_views == 1 %}
{% trans "It is automatically removed after the next view." %}
{% else %}
{% trans "It cannot be viewed again." %}
{% endif %}
</p>
{% endif %}
{% if snippet.lexer == 'text' %}
<div class="snippet-text">{% include "dpaste/highlight/text.html" %}</div>
{% else %}
<div class="snippet-code">{% include "dpaste/highlight/code.html" %}</div>
{% endif %}
<header class="sub">
<h2>{% trans "Edit this Snippet" %}</h2>
</header>
{% include "dpaste/includes/form.html" %}
{% endblock %}

View file

@ -1 +1 @@
<ol>{% for line in highlighted %}<li id="l{{ forloop.counter }}">{{ line|safe|default:"&nbsp;" }}</li>{% endfor %}</ol>
<ol>{% for line in snippet.highlight_lines %}<li id="l{{ forloop.counter }}">{{ line|safe|default:"&nbsp;" }}</li>{% endfor %}</ol>

View file

@ -1 +1 @@
{{ snippet.content|linebreaksbr }}
<div>{{ snippet.content|linebreaksbr }}</div>

View file

@ -0,0 +1,49 @@
{% extends "dpaste/base.html" %}
{% load i18n %}
{% block title %}{% trans "Snippet History" %}{% endblock %}
{% block body_type %}data-code-snippet{% endblock %}
{% block options %}
<ul id="snippetOptions">
<li>{% trans "Snippet History" %}</li>
{% if snippet_list %}
<li class="sep"></li>
<li>
<a href="?delete-all"
onclick="return confirm('{% trans "Are you sure you want to delete all snippets?" %}');">{% trans "Delete all Snippets" %}</a>
</li>
<li>
<label for="wordwrap">
{# Wordwrap is enabled by default for all lexer in the history #}
<input type="checkbox" id="wordwrap" checked> Wordwrap
</label>
</li>
{% endif %}
</ul>
{% endblock %}
{% block page %}
{% for snippet in snippet_list %}
<h3 class="history-header">
<a href="{{ snippet.get_absolute_url }}">{% blocktrans with snippet.lexer_name as type %}{{ type }} Snippet{% endblocktrans %}</a>
<span class="sep"></span>
{% blocktrans with snippet.published|timesince as since %}{{ since }} ago{% endblocktrans %}
</h3>
{% if snippet.lexer == 'text' %}
<div class="snippet-text">{% include "dpaste/highlight/text.html" %}</div>
{% else %}
<div class="snippet-code">{% include "dpaste/highlight/code.html" %}</div>
{% endif %}
{% empty %}
<p class="history-empty">
{% trans "No snippets saved. Either all your snippets are expired or your cookie has changed." %}</p>
{% endfor %}
{% endblock %}

View file

@ -0,0 +1,35 @@
{% load i18n %}
<form method="post" action="" class="snippet-form {% if new %}is-new{% endif %}">
{% csrf_token %}
{{ form.non_field_errors }}
<fieldset class="options">
<p style="display: none;">{{ form.title }}</p> {# Honeypot field #}
<p class="options-lexer">
<label for="{{ form.lexer.auto_id }}">{% trans "Syntax" %}</label>
{{ form.lexer }}
</p>
<p class="options-expire">
<label for="{{ form.expires.auto_id }}">{% trans "Expires" %}</label>
{{ form.expires }}
</p>
<p class="action">
<button class="btn" type="submit">
{% trans "Paste Snippet" %}
<span class="sep"></span>
<span class="platform-mac">{% trans "&#8984;+&#9166;" %}</span>
<span class="platform-win">{% trans "Ctrl+&#9166;" %}</span>
</button>
</p>
</fieldset>
<p class="content superenter {% if not object %}autofocus{% endif %} {% if form.content.errors %}error{% endif %}">
<label for="{{ form.content.auto_id }}">{% trans "Content" %}</label>
{{ form.content }}
</p>
</form>

View file

@ -0,0 +1,11 @@
{% extends "dpaste/base.html" %}
{% load i18n %}
{% block nav_new %}{% endblock %}
{% block title %}{% trans "dpaste" %}{% endblock %}
{% block page %}
{% include "dpaste/includes/form.html" %}
{% endblock %}

View file

@ -1,116 +0,0 @@
{% extends "dpaste/base.html" %}
{% load i18n %}
{% block title %}dpaste{% endblock %}
{% block headline %}
<input type="text" value="{{ request.build_absolute_uri }}"/>
{% endblock %}
{% block page %}
<!-- ======================================================================
Snippet Options
======================================================================= -->
<div class="snippet-options">
<span>
{% if snippet.expire_type == 1 %}
{% blocktrans with date=snippet.expires|timeuntil %}Expires in: {{ date }}{% endblocktrans %}
{% elif snippet.expire_type == 2 %}
{% trans "Snippet never expires" %}
{% elif snippet.expire_type == 3 %}
{% trans "One-Time snippet" %}
{% endif %}
</span>
<a href="{% url "snippet_delete" snippet.secret_id %}"
onclick="return confirm('{% trans "Really delete this snippet?" %}');">{% trans "Delete Now" %}</a>
{% if snippet.parent %}
<a href="#snippet-diff">{% trans "Compare with previous Snippet" %}</a>
{% endif %}
{% if snippet.expire_type != 3 %}
<a href="{% url "snippet_details_raw" snippet.secret_id %}">{% trans "View Raw" %}</a>
{% endif %}
{% if snippet.lexer != 'text' %}
<a href="#" id="toggleWordwrap">Wordwrap</a>
{% endif %}
</div>
{% if snippet.expire_type == 3 %}
<p class="message">
{% trans "This is a one-time snippet." %}
{% if snippet.remaining_views > 1 %}
{% blocktrans with remaining=snippet.remaining_views %}It is automatically removed after {{ remaining }} further views.{% endblocktrans %}
{% elif snippet.remaining_views == 1 %}
{% trans "It is automatically removed after the next view." %}
{% else %}
{% trans "It cannot be viewed again." %}
{% endif %}
</p>
{% endif %}
<!-- ======================================================================
Snippet
======================================================================= -->
{% if snippet.lexer == 'text' %}
<div class="snippet-text">{% include "dpaste/highlight/text.html" %}</div>
{% else %}
<div class="snippet-code">{% include "dpaste/highlight/code.html" %}</div>
{% endif %}
<!-- ======================================================================
Snippet Reply
======================================================================= -->
<div class="snippet-reply" data-hidden="yes">
<h2 class="box">{% trans "Reply to this snippet" %} &rarr;</h2>
{% include "dpaste/snippet_form.html" %}
</div>
{% endblock %}
{% block script_footer %}
{{ block.super }}
<script>
let reply = document.querySelector('.snippet-reply');
reply.onclick = function(e) {
this.dataset.hidden = "no";
};
document.getElementById('toggleWordwrap').onclick = function(e) {
e.preventDefault();
document.querySelector('.snippet-code').classList.toggle('wordwrap');
};
// Line Highlighting
let curLine = document.location.hash;
if (curLine.startsWith('#L')) {
hashlist = curLine.substring(2).split(',');
if (hashlist.length > 0 && hashlist[0] !== '') {
hashlist.forEach(function(el) {
let line = document.getElementById(`l${el}`);
if (line)
line.classList.add('marked');
});
}
}
let lines = document.querySelectorAll('.snippet-code li');
lines.forEach(function(el){
el.onclick = function(e) {
el.classList.toggle('marked');
let hash = 'L';
let marked = document.querySelectorAll('.snippet-code li.marked');
marked.forEach(function(line){
if (hash !== 'L')
hash += ',';
hash += line.getAttribute('id').substring(1);
});
window.location.hash = hash;
}
});
</script>
{% endblock %}

View file

@ -1,29 +0,0 @@
{% load i18n %}
<form method="post" action="" class="snippet-form">
{% csrf_token %}
{{ form.non_field_errors }}
<div style="display: none;">{{ form.title }}</div> {# Honeypot field #}
<div class="form-content superenter {% if not object %}autofocus{% endif %} {% if form.content.errors %}error{% endif %}">
{{ form.content }}
</div>
<div class="form-options">
<div class="form-options-lexer">
<label for="id_lexer">{% trans "Syntax" %}</label>
{{ form.lexer }}
</div>
<div class="form-options-expire">
<label for="id_expires">{% trans "Expires in" %}</label>
{{ form.expires }}
</div>
</div>
<div class="form-actions">
<input type="submit" value="{% trans "Paste it" %}">
<span class="shortcut">&#8984;+&#9166; {% trans "or" %} Ctrl+&#9166;</span>
</div>
</form>

View file

@ -1,28 +0,0 @@
{% extends "dpaste/base.html" %}
{% load i18n %}
{% block title %}{% trans "Snippet History" %}{% endblock %}
{% block headline %}{% trans "Snippet History" %}{% endblock %}
{% block page %}
<dl class="snippet-list">
{% for snippet in snippet_list %}
<dt>
<a title="{{ snippet.published|date:_("DATETIME_FORMAT") }}"
href="{{ snippet.get_absolute_url }}">
{% blocktrans with snippet.published|timesince as since %}{{ since }}
ago{% endblocktrans %}
</a>
</dt>
<dd>
{{ snippet.excerpt }}
</dd>
{% empty %}
<dt>{% trans "No snippets saved. Either all your snippets are expired or your cookie has changed." %}</dt>
{% endfor %}
<dt><a href="?delete-all">{% trans "Delete all your snippets" %}</a>
</dt>
</dl>
{% endblock %}

View file

@ -1,12 +0,0 @@
{% extends "dpaste/base.html" %}
{% load i18n %}
{% block title %}dpaste{% endblock %}
{% block headline %}dpaste{% endblock %}
{% block page %}
<div class="snippet-new">
{% include "dpaste/snippet_form.html" %}
</div>
{% endblock %}

View file

@ -21,10 +21,9 @@ from django.views.generic.detail import DetailView
from pygments.lexers import get_lexer_for_filename
from pygments.util import ClassNotFound
from .forms import EXPIRE_CHOICES, SnippetForm, get_expire_values
from .highlight import LEXER_DEFAULT, LEXER_KEYS, LEXER_LIST, LEXER_WORDWRAP, \
PLAIN_CODE, pygmentize
from .models import ONETIME_LIMIT, Snippet
from dpaste import highlight
from dpaste.forms import EXPIRE_CHOICES, SnippetForm, get_expire_values
from dpaste.models import ONETIME_LIMIT, Snippet
# -----------------------------------------------------------------------------
@ -36,7 +35,7 @@ class SnippetView(FormView):
Create a new snippet.
"""
form_class = SnippetForm
template_name = 'dpaste/snippet_new.html'
template_name = 'dpaste/new.html'
def get_form_kwargs(self):
kwargs = super(SnippetView, self).get_form_kwargs()
@ -48,7 +47,7 @@ class SnippetView(FormView):
def get_context_data(self, **kwargs):
ctx = super(SnippetView, self).get_context_data(**kwargs)
ctx.update({
'lexer_list': LEXER_LIST,
'lexer_list': highlight.LEXER_LIST,
})
return ctx
@ -63,7 +62,7 @@ class SnippetDetailView(SnippetView, DetailView):
tree/diff view.
"""
queryset = Snippet.objects.all()
template_name = 'dpaste/snippet_details.html'
template_name = 'dpaste/details.html'
slug_url_kwarg = 'snippet_id'
slug_field = 'secret_id'
@ -98,18 +97,10 @@ class SnippetDetailView(SnippetView, DetailView):
ctx = super(SnippetDetailView, self).get_context_data(**kwargs)
ctx.update({
'highlighted': self.highlight_snippet().splitlines(),
'wordwrap': snippet.lexer in LEXER_WORDWRAP and 'True' or 'False',
'wordwrap': snippet.lexer in highlight.LEXER_WORDWRAP,
})
return ctx
def highlight_snippet(self):
snippet = self.get_object()
h = pygmentize(snippet.content, snippet.lexer)
h = h.replace(u'\t', '&nbsp;&nbsp;&nbsp;&nbsp;')
return h
class SnippetRawView(SnippetDetailView):
"""
Display the raw content of a snippet
@ -143,7 +134,7 @@ class SnippetHistory(TemplateView):
Display the last `n` snippets created by this user (and saved in his
session).
"""
template_name = 'dpaste/snippet_list.html'
template_name = 'dpaste/history.html'
def get(self, request, *args, **kwargs):
snippet_id_list = request.session.get('snippet_list', [])
@ -167,7 +158,7 @@ class SnippetDiffView(TemplateView):
"""
Display a diff between two given snippet secret ids.
"""
template_name = 'dpaste/snippet_diff.html'
template_name = 'dpaste/includes/diff.html'
def get(self, request, *args, **kwargs):
"""
@ -209,8 +200,7 @@ class SnippetDiffView(TemplateView):
return diff
def highlight_snippet(self, content):
h = pygmentize(content, 'diff')
h = h.replace(u' ', u'&nbsp;&nbsp;')
h = highlight.pygmentize(content, 'diff')
h = h.replace(u'\t', '&nbsp;&nbsp;&nbsp;&nbsp;')
return h
@ -284,7 +274,7 @@ class APIView(View):
"""
def post(self, request, *args, **kwargs):
content = request.POST.get('content', '')
lexer = request.POST.get('lexer', LEXER_DEFAULT).strip()
lexer = request.POST.get('lexer', highlight.LEXER_DEFAULT).strip()
filename = request.POST.get('filename', '').strip()
expires = request.POST.get('expires', '').strip()
format = request.POST.get('format', 'default').strip()
@ -295,12 +285,12 @@ class APIView(View):
# We need at least a lexer or a filename
if not lexer and not filename:
return HttpResponseBadRequest('No lexer or filename given. Unable to '
'determine a highlight. Valid lexers are: %s' % ', '.join(LEXER_KEYS))
'determine a highlight. Valid lexers are: %s' % ', '.join(highlight.LEXER_LIST))
# A lexer is given, check if its valid at all
if lexer and lexer not in LEXER_KEYS:
if lexer and lexer not in highlight.LEXER_KEYS:
return HttpResponseBadRequest('Invalid lexer "%s" given. Valid lexers are: %s' % (
lexer, ', '.join(LEXER_KEYS)))
lexer, ', '.join(highlight.LEXER_KEYS)))
# No lexer is given, but we have a filename, try to get the lexer out of it.
# In case Pygments cannot determine the lexer of the filename, we fallback
@ -310,7 +300,7 @@ class APIView(View):
lexer_cls = get_lexer_for_filename(filename)
lexer = lexer_cls.aliases[0]
except (ClassNotFound, IndexError):
lexer = PLAIN_CODE
lexer = highlight.PLAIN_CODE
if expires:
expire_options = [str(i) for i in dict(EXPIRE_CHOICES).keys()]

1412
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

13
package.json Normal file
View file

@ -0,0 +1,13 @@
{
"name": "dpaste",
"version": "3.0.0",
"scripts": {
"build": "node-sass --output-style compressed -o build client/scss/dpaste.scss && uglifyjs -o build/dpaste.js client/js/dpaste.js",
"watch-css": "npm run build && node-sass --source-map true -o build/ --watch client/scss/dpaste.scss ",
"postinstall": "npm build"
},
"dependencies": {
"node-sass": "^4.7.2",
"uglify-es": "^3.3.9"
}
}

View file

@ -1,3 +0,0 @@
#!/usr/bin/env bash
node-sass --output-style compressed --output dpaste/static/dpaste/ dpaste/static/dpaste/dpaste.scss $1