435 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			HTML
		
	
	
		
			Executable file
		
	
	
	
	
			
		
		
	
	
			435 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			HTML
		
	
	
		
			Executable file
		
	
	
	
	
<!doctype html>
 | 
						|
<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://github.com/arcanis/secretsanta">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 wanted to make a Secret Santa over Facebook without having to reveal to anyone my guests email addresses (so nothing that would require a backend). I also wanted not to know who was paired with me, so I had to find a way to somehow obfuscate the information. And being a developer, well, my first thought was "Let's AES it, for fun and profits!". Classic.</p>
 | 
						|
                </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)
 | 
						|
 | 
						|
            # 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(/[!=][^!=]+/) : 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>
 |