Compare commits
10 commits
Author | SHA1 | Date | |
---|---|---|---|
f4bc57ffd4 | |||
a2abbf41a0 | |||
|
75dc799599 | ||
|
627eaa57f6 | ||
|
c5d8220766 | ||
|
d335fb919b | ||
|
48e55aafab | ||
|
6ad707ddba | ||
|
f2cd4ed1ee | ||
|
27d171c6d0 |
4 changed files with 254 additions and 45 deletions
|
@ -2,6 +2,7 @@ var SecretSanta = function () {
|
|||
|
||||
this.names = [];
|
||||
|
||||
this.enforced = Object.create( null );
|
||||
this.blacklists = Object.create( null );
|
||||
};
|
||||
|
||||
|
@ -15,6 +16,14 @@ SecretSanta.prototype.add = function ( name ) {
|
|||
|
||||
var subapi = { };
|
||||
|
||||
subapi.enforce = function ( other ) {
|
||||
|
||||
this.enforced[ name ] = other;
|
||||
|
||||
return subapi;
|
||||
|
||||
}.bind( this );
|
||||
|
||||
subapi.blacklist = function ( other ) {
|
||||
|
||||
if ( ! Object.prototype.hasOwnProperty.call( this.blacklists, name ) )
|
||||
|
@ -38,12 +47,33 @@ SecretSanta.prototype.generate = function () {
|
|||
|
||||
this.names.forEach( function ( name ) {
|
||||
|
||||
var candidates = _.difference( this.names, [ name ] );
|
||||
if ( Object.prototype.hasOwnProperty.call( this.enforced, name ) ) {
|
||||
|
||||
if ( Object.prototype.hasOwnProperty.call( this.blacklists, name ) )
|
||||
candidates = _.difference( candidates, this.blacklists[ name ] );
|
||||
var enforced = this.enforced[ name ];
|
||||
|
||||
candidatePairings[ name ] = candidates;
|
||||
if ( this.names.indexOf( enforced ) === -1 )
|
||||
throw new Error( name + ' is paired with ' + enforced + ', which hasn\'t been declared as a possible pairing' );
|
||||
|
||||
Object.keys( pairings ).forEach( function ( name ) {
|
||||
|
||||
if ( pairings[ name ] === enforced ) {
|
||||
throw new Error( 'Per your rules, multiple persons are paired with ' + enforced );
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
candidatePairings[ name ] = [ this.enforced[ name ] ];
|
||||
|
||||
} else {
|
||||
|
||||
var candidates = _.difference( this.names, [ name ] );
|
||||
|
||||
if ( Object.prototype.hasOwnProperty.call( this.blacklists, name ) )
|
||||
candidates = _.difference( candidates, this.blacklists[ name ] );
|
||||
|
||||
candidatePairings[ name ] = candidates;
|
||||
|
||||
}
|
||||
|
||||
}, this );
|
||||
|
||||
|
|
BIN
assets/snow.png
Normal file
BIN
assets/snow.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.4 KiB |
130
index.html
130
index.html
|
@ -1,4 +1,3 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
|
@ -131,9 +130,9 @@
|
|||
}
|
||||
|
||||
.result-name {
|
||||
width: 20%;
|
||||
width: 30%;
|
||||
|
||||
padding-right: 5px 8px;
|
||||
padding: 5px 8px;
|
||||
}
|
||||
|
||||
.result-link {
|
||||
|
@ -152,16 +151,34 @@
|
|||
|
||||
<div class="main">
|
||||
|
||||
<div class="part">
|
||||
<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://github.com/arcanis/secretsanta">open-source</a> tool to help you generate your secret santa pairings. Static page only.</p>
|
||||
<p>The DSL that is used to set the pairing rules is dead simple: in the most common use case (no special rule, pair each guest to another), you just have to enter the name of your guests, one line at a time. Once done, press "generate" and voilà, you will get a set of links that you will just have to give to each one of them (by mail, chat, whatever float your boat). Once they access the link, their pairing will be revealed (to them and only them).</p>
|
||||
<p>Should you have more complex needs (for example if you want to prevent a couple from being paired together, or want to prevent someone to be paired with someone else they don't know), just append <code>!<name></code> after a guest name, and he will never be paired with <code><name></code> (check on the right for an example).</p>
|
||||
<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>I just wanted to make a Secret Santa over Facebook without giving up my guests email addresses to do so (so nothing that requires a backend). I also wanted to ignore who was paired with me, so I had to find a way to obfuscate it. And since I'm a developer, well, I just thought "Let's AES it, for fun and profits!". Classic.</p>
|
||||
<!-- <p>If you want to tip me, feel free to send me anything on the following Bitcoin address! I'll probably use it on some 3DS game (Fire Emblem, maybe?) :) Anyway, merry christmas!</p>
|
||||
<p><img src="assets/bitcoin.ico" style="vertical-align: middle" /> <a href="bitcoin:1HLgRVCGcXabWhMfdLazFVUWiH2W1kCDQK"><code>1HLgRVCGcXabWhMfdLazFVUWiH2W1kCDQK</code></a></p>-->
|
||||
<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>
|
||||
|
||||
|
@ -177,10 +194,21 @@
|
|||
# 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>
|
||||
|
||||
|
@ -188,13 +216,23 @@
|
|||
|
||||
<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 );
|
||||
|
||||
}
|
||||
|
@ -204,7 +242,17 @@
|
|||
if ( ! window.localStorage )
|
||||
return ;
|
||||
|
||||
var content = window.localStorage.getItem( 'input' ) || '';
|
||||
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;
|
||||
|
||||
|
@ -248,6 +296,7 @@
|
|||
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';
|
||||
|
@ -268,13 +317,37 @@
|
|||
var encryptedPairing = CryptoJS.AES.encrypt( pairings[ name ], key );
|
||||
|
||||
var pairingPath = window.location.pathname.replace( /[^/]+$/, '' ) + 'pairing.html';
|
||||
var pairingQueryString = '?name=' + encodeURIComponent( name ) + '&key=' + key + '&pairing=' + encryptedPairing;
|
||||
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;
|
||||
|
||||
}
|
||||
|
@ -302,8 +375,8 @@
|
|||
// Strip empty lines
|
||||
content = content.replace( /\n+/g, '\n' );
|
||||
|
||||
// Remove trailing newline
|
||||
content = content.replace( /\n$/, '' );
|
||||
// Remove leading/trailing newlines
|
||||
content = content.replace( /^\n|\n$/g, '' );
|
||||
|
||||
var lines = content.split( /\n/g );
|
||||
|
||||
|
@ -314,18 +387,31 @@
|
|||
|
||||
for ( var t = 0, T = lines.length; t < T; ++ t ) {
|
||||
|
||||
var match = lines[ t ].match( /^([^ ]+)(?: ((?:![^ ]+)(?: ![^ ]+)*))?$/ );
|
||||
var match = lines[ t ].match( /^((?:(?![!=]).)+)((?: [!=](?:(?! [!=]).)+)*)$/ );
|
||||
|
||||
if ( ! match )
|
||||
return error( 'Syntax error: "' + lines[ t ] + '" isn\'t a valid line' );
|
||||
return error( 'Syntax error: "' + lines[ t ] + '" isn\'t valid' );
|
||||
|
||||
var name = match[ 1 ];
|
||||
var exclusions = match[ 2 ] ? match[ 2 ].replace( /!/g, '' ).split( / /g ) : [];
|
||||
var rules = match[ 2 ] ? match[ 2 ].match(/[!=][^!=]+/g) : null;
|
||||
|
||||
var person = santa.add( name );
|
||||
|
||||
for ( var u = 0, U = exclusions.length; u < U; ++ u ) {
|
||||
person.blacklist( exclusions[ u ] );
|
||||
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() );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -355,6 +441,12 @@
|
|||
|
||||
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();
|
||||
|
|
131
pairing.html
131
pairing.html
|
@ -25,19 +25,71 @@
|
|||
}
|
||||
|
||||
body {
|
||||
background-image: url(assets/santa.png);
|
||||
background-position: bottom right;
|
||||
background-repeat: no-repeat;
|
||||
background: url(./assets/snow.png), url(./assets/santa.png), url(./assets/snow.png), radial-gradient(#FB3B3B, #EF3D3D);
|
||||
background-repeat: repeat, no-repeat, repeat, no-repeat;
|
||||
|
||||
animation-name: snow;
|
||||
animation-duration: 5s;
|
||||
animation-iteration-count: infinite;
|
||||
animation-timing-function: linear;
|
||||
|
||||
font-family: 'Comic Sans MS';
|
||||
}
|
||||
|
||||
@keyframes snow {
|
||||
from { background-position: 130px 40px, bottom right, 0 0, 0 0; }
|
||||
to { background-position: 130px 640px, bottom right, 0 300px, 0 0; }
|
||||
}
|
||||
|
||||
.spirit-of-christmas {
|
||||
display: block;
|
||||
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
|
||||
padding: 20px;
|
||||
|
||||
font-size: 12px;
|
||||
text-decoration: none;
|
||||
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.main {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.content {
|
||||
.background {
|
||||
position: absolute;
|
||||
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
margin: auto;
|
||||
padding: 20px;
|
||||
|
||||
background: repeating-linear-gradient(
|
||||
45deg,
|
||||
#5CC48A,
|
||||
#5CC48A 30px,
|
||||
#FFFFFF 30px,
|
||||
#FFFFFF 60px,
|
||||
#EF3D3D 60px,
|
||||
#EF3D3D 90px,
|
||||
#FFFFFF 90px,
|
||||
#FFFFFF 120px
|
||||
);
|
||||
|
||||
box-shadow: 3px 3px 10px rgba(0, 0, 0, .5);
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 40px;
|
||||
|
||||
background: #ffffff;
|
||||
|
||||
text-align: center;
|
||||
}
|
||||
|
@ -46,18 +98,24 @@
|
|||
font-size: 30px;
|
||||
}
|
||||
|
||||
.pairing {
|
||||
#pairing-name {
|
||||
font-size: 90px;
|
||||
}
|
||||
|
||||
#pairing-details {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.affiliate {
|
||||
display: block;
|
||||
box-sizing: content-box;
|
||||
|
||||
margin-top: 70px;
|
||||
margin-top: 40px;
|
||||
|
||||
border: 10px solid #0CB50C;
|
||||
border-radius: 10px;
|
||||
border: none;
|
||||
|
||||
background: rgba(255, 255, 255, .7);
|
||||
}
|
||||
|
@ -68,14 +126,16 @@
|
|||
|
||||
</style>
|
||||
|
||||
<script>1
|
||||
<script>
|
||||
|
||||
var queryString = _.chain( location.search.slice( 1 ).split( /&/g ) )
|
||||
.map( function ( item ) { if ( item ) return item.split( /=/ ).map( function ( str ) { return decodeURIComponent( str ); } ); } )
|
||||
.compact().object().value();
|
||||
|
||||
var name = queryString.name;
|
||||
|
||||
var pairing = CryptoJS.AES.decrypt( queryString.pairing, queryString.key ).toString(CryptoJS.enc.Utf8);
|
||||
var pairingDefinition = pairing.match( /^([^(]+)(?: \(([^)]+)\))?$/ );
|
||||
|
||||
</script>
|
||||
|
||||
|
@ -85,22 +145,49 @@
|
|||
|
||||
<div class="main">
|
||||
|
||||
<div class="content">
|
||||
|
||||
<h3 class="title">Hi <span id="name"></span>! You've been paired with</h3>
|
||||
<script>document.getElementById('name').innerText = name</script>
|
||||
<div class="wrapper">
|
||||
<div class="content">
|
||||
|
||||
<h1 class="pairing"><span id="pairing"></span></h1>
|
||||
<script>document.getElementById('pairing').innerText = pairing</script>
|
||||
|
||||
<h3 class="title">Good luck!</h3>
|
||||
|
||||
<!--
|
||||
<iframe class="affiliate" src="http://rcm-eu.amazon-adsystem.com/e/cm?t=secrsant02e-21&o=8&p=48&l=ur1&category=jeuxetjouets&banner=0HS03ACZ89HPK7F4F5G2&f=ifr" width="728" height="90" scrolling="no" border="0" marginwidth="0" frameborder="0"></iframe>
|
||||
<iframe class="affiliate" src="http://rcm-eu.amazon-adsystem.com/e/cm?t=secrsant02e-21&o=8&p=48&l=ur1&category=books&banner=10DQAXJ7D1D2VTXMR682&f=ifr" width="728" height="90" scrolling="no" border="0" marginwidth="0" frameborder="0"></iframe>
|
||||
-->
|
||||
<div class="title">
|
||||
Hej <span id="name"></span>!<br>
|
||||
Your secret child is (drum roll<span id="drum-roll"></span>)
|
||||
</div>
|
||||
<script>document.getElementById('name').innerText = name</script>
|
||||
|
||||
<div class="pairing" id="pairing-box">
|
||||
<div id="pairing-name"></div>
|
||||
<div id="pairing-details"></div>
|
||||
<script>
|
||||
document.getElementById('pairing-name').innerText = pairingDefinition[1];
|
||||
|
||||
if (pairingDefinition[2]) {
|
||||
document.getElementById('pairing-details').innerHTML = pairingDefinition[2].replace(/\|/g, '<br>');
|
||||
} else {
|
||||
document.getElementById('pairing-details').style.display = 'none';
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
<script>
|
||||
document.getElementById('pairing-box').style.color = 'white';
|
||||
intervalId = window.setInterval(function() {
|
||||
document.getElementById('drum-roll').innerText += '.';
|
||||
}, 200);
|
||||
window.setTimeout(function() {
|
||||
document.getElementById('pairing-box').style.color = 'inherit';
|
||||
clearInterval(intervalId);
|
||||
}, 3000);
|
||||
</script>
|
||||
|
||||
<div class="title">
|
||||
May you bring them joy and happiness with your gift!
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a href="https://louis.hostux.fr/secretsanta/" class="spirit-of-christmas">
|
||||
Want to start your own Secret Santa with your friends? Click here to get started!
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
|
Loading…
Reference in a new issue