secretsanta/index.html

456 lines
13 KiB
HTML
Executable file

<html>
<head>
<meta charset="utf-8" />
<title>Secret Santa Generator</title>
<script src="vendors/Lodash-3.10.1.js"></script>
<script src="vendors/Cryptojs.aes-3.1.2.js"></script>
<script src="./SecretSanta.js"></script>
<style>
* {
box-sizing: border-box;
}
html, body, .main {
margin: 0;
width: 100%;
height: 100%;
padding: 0;
}
body {
background-image: url(assets/santa.png);
background-position: bottom right;
background-repeat: no-repeat;
font-family: 'Comic Sans MS';
}
.main {
display: flex;
}
.part {
display: flex;
flex-direction: column;
padding: 20px;
flex: 1;
}
.instructions {
margin: auto 10%;
}
.instructions a {
text-decoration: none;
color: #0CB50C;
}
.input {
display: block;
align-self: stretch;
width: 100%;
flex: auto;
border: 10px solid #0CB50C;
border-radius: 10px;
padding: 15px;
background-color: rgba(255, 255, 255, .7);
outline: none;
}
.generate {
display: block;
margin-top: 20px;
flex: none;
height: 100px;
border: 10px solid #0CB50C;
border-radius: 10px;
font-family: inherit;
font-size: 40px;
background-color: #EA1212;
color: #FFFFFF;
}
.generate:focus {
border-color: #E6E020;
outline: none;
}
.result {
margin-top: 20px;
flex: none;
border: 10px solid #E6E020;
border-radius: 10px;
padding: 15px;
background: #FFFFFF;
}
.result a {
color: blue;
}
.result.none {
display: none;
}
.result.error {
border-color: #EA1212;
color: #EA1212;
}
.result-table {
table-layout: fixed;
width: 100%;
}
.result-name {
width: 30%;
padding: 5px 8px;
}
.result-link {
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>
</head>
<body>
<div class="main">
<div class="part" style="overflow-y: auto">
<div class="instructions">
<h1><img src="assets/mistletoe.png" style="vertical-align: middle" /> Secret Santa Generator</h1>
<p>No signup, no email, no bullshit. Just a straightforward <a href="https://git.hostux.fr/louis/secretsanta" target="_blank">open-source</a> tool to help you generate your secret santa pairings. One static page, and that's it.</p>
<p>In the most common case (no exclusion rules, pair each guest with another at random), enter the name of your guests one line at a time. Once done, press "generate" and you're all set: send the generated links to your guests (by mail, chat, whatever floats your boat) and their pairing will be revealed to them (and only them) once they open the link.</p>
<h2>Where does this tool come from?</h2>
<p>This tool has been <a href="https://git.hostux.fr/louis/secretsanta" target="_blank">forked</a> from <a href="https://github.com/arcanis/secretsanta" target="_blank">arcanis/secretsanta</a>. It is self-hosted on <a href="https://hostux.fr" target="_blank">hostux.fr</a>.</p>
<h2>What about my privacy?</h2>
<p>There is no backend, i.e. no data stored on my server! The links that are generated contain all the encrypted information.</p>
<h2>How to use?</h2>
<pre># You can add a user by adding a line
Santa
# You can add some details if you want to, using parentheses after the name
Nicholas (the elf)
Nicholas (the elf|with a second line)
# You can prevent someone from being paired with someone else
Maël !Aurélie
Aurélie !Maël
You can also exclude someone from being paired with multiple people
Careful: too many exclusion rules can make your secret santa less interesting!
Rudolph !Santa !Nicholas (the elf)
You can also cheat a bit and force someone to be paired with another
Nicholas (the saint) =Nicholas (the elf)
...</pre>
</div>
</div>
<form id="form" class="part">
<textarea id="input" class="input" autofocus></textarea>
<button type="submit" class="generate">Generate your pairings</button>
<div id="result" class="result none"></div>
</form>
</div>
<script id="input-placeholder" type="text/placeholder">
# You can add a user by adding a line
Santa
# You can add some details if you want to, using parentheses after the name
Nicholas (the elf)
Nicholas (the elf|with a second line)
# You can prevent someone from being paired with someone else
Maël !Aurélie
Aurélie !Maël
# You can also exclude someone from being paired with multiple people
# Careful: too many exclusion rules can make your secret santa less interesting!
Rudolph !Santa !Nicholas (the elf)
# You can also cheat a bit and force someone to be paired with another
Nicholas (the saint) =Nicholas (the elf)
...
</script>
</body>
<script>
function isAmazonEnabled() {
const checkBox = document.getElementById('amazon');
return checkBox ? checkBox.checked : false;
}
function persist() {
if ( ! window.localStorage )
return ;
var amazon = isAmazonEnabled();
var content = document.getElementById( 'input' ).value;
window.localStorage.setItem( 'amazon', amazon );
window.localStorage.setItem( 'input', content );
}
function restore() {
if ( ! window.localStorage )
return ;
var amazon = window.localStorage.getItem( 'amazon' );
var content = window.localStorage.getItem( 'input' );
if (typeof amazon === 'undefined')
amazon = true;
if (typeof content === 'undefined')
content = '';
if ( document.getElementById( 'amazon' ) )
document.getElementById( 'amazon' ).checked = amazon;
document.getElementById( 'input' ).value = content;
}
function reset() {
var result = document.getElementById( 'result' );
result.classList.add( 'none' );
result.classList.remove( 'error' );
}
function error(text) {
var result = document.getElementById( 'result' );
result.classList.remove( 'none' );
result.classList.add( 'error' );
result.innerText = text;
}
function success( pairings ) {
var result = document.getElementById( 'result' );
result.classList.remove( 'none' );
result.classList.remove( 'error' );
result.innerHTML = '';
var table = document.createElement( 'table' );
table.className = 'result-table';
result.appendChild( table );
var names = Object.keys( pairings ).sort();
for ( var t = 0, T = names.length; t < T; ++ t ) {
var name = names[ t ];
var prettyName = names[ t ].replace( /\([^)]+\)/g, ' ' ).replace( / +/g, ' ' ).trim();
var tr = document.createElement( 'tr' );
tr.className = 'result-row';
table.appendChild( tr );
var tdName = document.createElement( 'td' );
tdName.className = 'result-name';
tr.appendChild( tdName );
var tdLink = document.createElement( 'td' );
tdLink.className = 'result-link';
tr.appendChild( tdLink );
var link = document.createElement( 'a' );
tdLink.appendChild( link );
var key = String( _.random( 0x0000, 0xFFFF ) );
var encryptedPairing = CryptoJS.AES.encrypt( pairings[ name ], key );
var pairingPath = window.location.pathname.replace( /[^/]+$/, '' ) + 'pairing.html';
var pairingQueryString = '?name=' + encodeURIComponent( prettyName ) + '&key=' + encodeURIComponent( key ) + '&pairing=' + encodeURIComponent( encryptedPairing );
if ( isAmazonEnabled() )
pairingQueryString += '&extra=1';
tdName.innerText = name;
link.addEventListener( 'click', protect );
link.setAttribute( 'data-name', name );
link.href = window.location.protocol + '//' + window.location.host + pairingPath + pairingQueryString;
link.target = '_blank';
link.innerText = link.href;
}
}
function updateAmazon() {
var result = document.getElementById( 'result' );
var links = result.getElementsByTagName( 'a' );
for ( var t = 0; t < links.length; ++t ) {
var link = links[t];
if ( isAmazonEnabled() )
link.href += '&extra=1';
else
link.href = link.href.replace( /&extra=[01]/, '' );
link.innerText = link.href;
}
}
function generate( e ) {
e.preventDefault();
var content = document.getElementById( 'input' ).value;
// Convert carriage returns into line feeds
content = content.replace( /(\r\n|\r)/g, '\n' );
// Merge adjacent blank characters into a single space
content = content.replace( /[ \t]+/g, ' ' );
// Trim lines
content = content.replace( /^ | $/gm, '' );
// Strip comments
content = content.replace( /^#.*$/gm, '' );
// Strip empty lines
content = content.replace( /\n+/g, '\n' );
// Remove leading/trailing newlines
content = content.replace( /^\n|\n$/g, '' );
var lines = content.split( /\n/g );
if ( lines.length === 0 || lines.length === 1 && lines[ 0 ].length === 0 )
return reset();
var santa = new SecretSanta();
for ( var t = 0, T = lines.length; t < T; ++ t ) {
var match = lines[ t ].match( /^((?:(?![!=]).)+)((?: [!=](?:(?! [!=]).)+)*)$/ );
if ( ! match )
return error( 'Syntax error: "' + lines[ t ] + '" isn\'t valid' );
var name = match[ 1 ];
var rules = match[ 2 ] ? match[ 2 ].match(/[!=][^!=]+/g) : null;
var person = santa.add( name );
if (rules) {
for ( var u = 0, U = rules.length; u < U; ++ u ) {
var fnName = {
'=': 'enforce',
'!': 'blacklist'
}[ rules[ u ].charAt( 0 ) ];
person[ fnName ]( rules[ u ].slice( 1 ).trim() );
}
}
}
try {
return success( santa.generate() );
} catch ( err ) {
console.error(err.stack)
return error( err.message );
}
}
function protect( e ) {
var name = e.currentTarget.getAttribute( 'data-name' );
if ( ! confirm( 'If you click this link, you will be revealed ' + name + '\'s pairing! Are you sure you want to do this? Only do this if you\'re actually ' + name + '.\n\nUse right-click to copy the link target instead.' ) ) {
e.preventDefault();
}
}
</script>
<script>
document.getElementById( 'input' ).placeholder = document.getElementById( 'input-placeholder' ).innerHTML.trim().replace( /^[ \t]+/gm, '' );
document.getElementById( 'input' ).addEventListener( 'change', persist );
if ( document.getElementById( 'amazon' ) ) {
document.getElementById( 'amazon' ).addEventListener( 'change', updateAmazon );
document.getElementById( 'amazon' ).addEventListener( 'change', persist );
}
document.getElementById( 'form' ).addEventListener( 'submit', generate );
restore();
</script>
</html>