├── .gitattributes ├── .gitignore ├── README.md ├── package.json ├── public ├── css │ └── style.css └── js │ └── script.js ├── server.js └── views └── index.jade /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear in the root of a volume 35 | .DocumentRevisions-V100 36 | .fseventsd 37 | .Spotlight-V100 38 | .TemporaryItems 39 | .Trashes 40 | .VolumeIcon.icns 41 | 42 | # Directories potentially created on remote AFP share 43 | .AppleDB 44 | .AppleDesktop 45 | Network Trash Folder 46 | Temporary Items 47 | .apdisk 48 | 49 | #lol 50 | node_modules/ 51 | *.txt -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rapidpoll 2 | 3 | ## about 4 | 5 | A rapid question and answer type app where you can post any question and comment upvotable answers to the questions. after 40 seconds the question and all the answers are deleted and the next question comes up. 6 | 7 | More features will be implemented soon (update: probably not) 8 | 9 | ## installation 10 | 11 | Use `npm install` to install dependencies (assuming you have node.js installed). `node server` to boot it up! 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rapidpoll", 3 | "version": "1.0.0", 4 | "description": "a rapid poll", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "William Ye", 10 | "license": "ISC", 11 | "dependencies": { 12 | "escape-html": "^1.0.3", 13 | "express": "^4.13.4", 14 | "jade": "^1.11.0", 15 | "serve-favicon": "^2.3.0", 16 | "socket.io": "^1.4.5" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /public/css/style.css: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css?family=Roboto:400,100,100italic,300,300italic,400italic,500,500italic,700,700italic,900italic,900); 2 | 3 | * { 4 | margin: 0; 5 | padding: 0; 6 | font-family: 'roboto'; 7 | box-sizing: border-box; 8 | -moz-box-sizing: border-box; 9 | -webkit-box-sizing: border-box; 10 | word-wrap: break-word; 11 | } 12 | body { 13 | padding: 0px 15px 0px 15px; 14 | } 15 | 16 | @keyframes colorRotate { 17 | from { 18 | color: rgb(255, 0, 0); 19 | } 20 | 16.6% { 21 | color: rgb(255, 0, 255); 22 | } 23 | 33.3% { 24 | color: rgb(0, 0, 255); 25 | } 26 | 50% { 27 | color: rgb(0, 255, 255); 28 | } 29 | 66.6% { 30 | color: rgb(0, 255, 0); 31 | } 32 | 83.3% { 33 | color: rgb(255, 255, 0); 34 | } 35 | to { 36 | color: rgb(255, 0, 0); 37 | } 38 | } 39 | 40 | #ip { 41 | display:none; 42 | } 43 | .rainbow { 44 | animation: colorRotate 1s linear 0s infinite; 45 | } 46 | 47 | #container { 48 | width:800px; 49 | margin:0 auto; 50 | margin-top: 50px; 51 | } 52 | 53 | #question-div { 54 | width:800px; 55 | padding:20px; 56 | text-align: center; 57 | border: solid 2px #dddddd; 58 | border-radius: 5px; 59 | margin-bottom: 15px; 60 | color:#333333; 61 | } 62 | 63 | #timer { 64 | position:absolute; 65 | height:5px; 66 | width:0px; 67 | background-color: #545454; 68 | border:solid 1px #545454; 69 | border-bottom-left-radius: 3px; 70 | margin:15px 0px 0px -20px; 71 | } 72 | 73 | #online { 74 | 75 | } 76 | 77 | #links { 78 | display:inline-block; 79 | margin-bottom: 10px; 80 | } 81 | 82 | 83 | 84 | #info { 85 | margin:0px 0px 5px 8px; 86 | font-size: 0.8em; 87 | } 88 | 89 | #top-div { 90 | margin-right: 12px; 91 | } 92 | 93 | #answer-list { 94 | display: inline-block; 95 | width:500px; 96 | border: solid 2px #dddddd; 97 | border-radius: 5px; 98 | height:500px; 99 | overflow:auto; 100 | } 101 | 102 | .vote-div { 103 | width:50px; 104 | display:inline-block; 105 | text-align: center; 106 | color:#545454; 107 | top:50%; 108 | } 109 | 110 | .upvote { 111 | color:#dbdbdb; 112 | } 113 | 114 | .upvote-clicked { 115 | color:#545454; 116 | } 117 | 118 | #info p { 119 | color:#aea9b1; 120 | } 121 | 122 | a { 123 | color:#545454; 124 | cursor: pointer; 125 | } 126 | 127 | .answer-sec { 128 | display: flex; 129 | align-items: center; 130 | } 131 | 132 | .score { 133 | position:relative; 134 | text-align: center; 135 | font-size: 0.75em; 136 | margin-top:-3px; 137 | } 138 | 139 | .answer-div { 140 | padding:10px 15px 10px 0px; 141 | width:310px; 142 | display:inline-block; 143 | color:#545454; 144 | } 145 | 146 | li { 147 | list-style: none; 148 | } 149 | 150 | h1 { 151 | font-weight: 700; 152 | font-size: 2.5em; 153 | color:#333333; 154 | display:inline-block; 155 | margin-bottom: 5px; 156 | } 157 | 158 | h2 { 159 | font-weight: 400; 160 | } 161 | 162 | #input-container { 163 | vertical-align: top; 164 | display:inline-block; 165 | width:290px; 166 | float:right; 167 | } 168 | 169 | .input-div { 170 | margin-bottom: 10px; 171 | 172 | border: solid 2px #dddddd; 173 | border-radius: 5px; 174 | 175 | /* 176 | display: flex; 177 | align-items: center; 178 | */ 179 | } 180 | 181 | input { 182 | width:250px; 183 | border:none; 184 | outline: none; 185 | padding:8px 0px 8px 12px; 186 | color:#333333; 187 | display:inline-block; 188 | } 189 | 190 | .submit-div { 191 | float:right; 192 | display:inline-block; 193 | color:#545454; 194 | width:35px; 195 | text-align: center; 196 | right:0; 197 | margin-top:5px; 198 | } 199 | 200 | i { 201 | vertical-align: middle; 202 | } 203 | 204 | #ad { 205 | width:290px; 206 | height:250px; 207 | display:inline-block; 208 | float:right; 209 | margin-top:-312px; 210 | } 211 | 212 | 213 | @media screen and (max-width:830px) { 214 | #container { 215 | width:100%; 216 | margin-top:20px; 217 | } 218 | #question-div { 219 | width:100%; 220 | } 221 | 222 | #answer-list { 223 | margin:0px auto 10px auto; 224 | display:block; 225 | width:100%; 226 | height:400px; 227 | 228 | } 229 | 230 | #input-container { 231 | margin:0px auto 0px auto; 232 | display:block; 233 | width:100%; 234 | } 235 | 236 | input { 237 | width: calc(100% - 40px); 238 | } 239 | 240 | h1 { 241 | font-size:2em; 242 | } 243 | 244 | #ad { 245 | margin:0 auto; 246 | float:none; 247 | } 248 | 249 | } -------------------------------------------------------------------------------- /public/js/script.js: -------------------------------------------------------------------------------- 1 | var socket = io(); 2 | $(document).ready(function() { 3 | 4 | var id = ""; 5 | socket.emit('join'); 6 | var mSecondsLeft = 0; 7 | var defaultQuestionId = 'n/a'; 8 | 9 | socket.on('join', function(data) { 10 | id = data.id; 11 | for (key in data.answers) { 12 | for (key2 in data.answers[key]) { 13 | $('#answer-list ul').append(getAnswerSec(data.answers[key][key2])); 14 | } 15 | } 16 | 17 | $('#question').html(data.question.question); 18 | if (data.question.id !== defaultQuestionId) { 19 | $("#answer-input").prop('disabled', false); 20 | $('#answer-input').attr('placeholder', 'post an answer'); 21 | } 22 | socket.emit('get queue'); 23 | }); 24 | 25 | function getAnswerSec(data) { 26 | 27 | var rainbowIndex = data.answer.indexOf('!rainbow'); 28 | var classP = ''; 29 | if (rainbowIndex > -1) { 30 | data.answer = data.answer.replace('!rainbow',''); 31 | classP = ' class="rainbow"'; 32 | } 33 | return "
  • arrow_upward

    " + data.score + "

    " + data.answer + "

  • "; 34 | console.log(data.answer); 35 | } 36 | 37 | socket.on('new question sent', function(data) { 38 | $('#question').html(data.question); 39 | document.title = 'rapidpoll: ' + data.question; 40 | socket.emit('get queue'); 41 | console.log('got new question: ' + data.question); 42 | $('li').remove(); 43 | if (id == data.id) { 44 | $("#question-input").prop('disabled', false); 45 | $('#question-input').attr('placeholder', 'ask a question'); 46 | $('#question-submit i').html('send'); 47 | $('#question-submit').attr('title', ''); 48 | } 49 | if (data.id !== defaultQuestionId) { 50 | $("#answer-input").prop('disabled', false); 51 | $('#answer-input').attr('placeholder', 'post an answer'); 52 | } else { 53 | $("#answer-input").prop('disabled', true); 54 | $('#answer-input').attr('placeholder', 'no question to answer'); 55 | $('#answer-input').val(''); 56 | } 57 | 58 | }); 59 | 60 | socket.on('new question entered', function(data) { 61 | socket.emit('get queue'); 62 | console.log('total questions: ' + data); 63 | }); 64 | 65 | socket.on('get queue', function(data) { 66 | if (data.place != 'n/a') { 67 | $('#question-input').attr('placeholder', 'position in queue: ' + data.place + '/' + data.total); 68 | } 69 | $('#queue').html('questions in queue: ' + data.total); 70 | }); 71 | 72 | socket.on('new answer', function(data) { 73 | $('#answer-list ul').append(getAnswerSec(data)); 74 | }); 75 | 76 | socket.on('upvote', function(data) { 77 | console.log('upvote gotten: ' + data.id); 78 | $('.score[answer-id="' + data.id + '"][number = "' + data.number + '"]').html(data.score); 79 | $('.score[answer-id="' + data.id + '"][number = "' + data.number + '"]').parent().parent().parent().attr('score', data.score); 80 | //sortAnswers(); 81 | 82 | }); 83 | 84 | socket.on('timer', function(data) { 85 | if (data.secondsLeft == 0) { 86 | $('#timer').css('width', $('#question-div').width() + 30); 87 | } else { 88 | $('#timer').css('width', ($('#question-div').width() + 20) - ((data.secondsLeft - 1) / data.questionDuration * $('#question-div').width() + 20)); 89 | } 90 | }); 91 | 92 | socket.on('clients online', function(data) { 93 | console.log(data); 94 | $('#online').html('clients online: ' + data); 95 | }); 96 | 97 | socket.on('max answers', function() { 98 | $("#answer-input").prop('disabled', true); 99 | $('#answer-input').val(''); 100 | $('#answer-input').attr('placeholder', 'max number of answers reached'); 101 | }); 102 | 103 | $("#question-input").keyup(function(event){ 104 | if(event.keyCode == 13){ 105 | $("#question-submit").click(); 106 | } 107 | }); 108 | 109 | $("#answer-input").keyup(function(event){ 110 | if(event.keyCode == 13){ 111 | $("#answer-submit").click(); 112 | } 113 | }); 114 | 115 | $('#question-submit').click(function() { 116 | if ($('#question-submit i').html() == 'send') { 117 | if (/\S/.test($('#question-input').val())) { 118 | socket.emit('submit question', $('#question-input').val()); 119 | $('#question-input').val(''); 120 | $("#question-input").prop('disabled', true); 121 | $('#question-submit i').html('clear'); 122 | $('#question-submit').attr('title', 'remove your question from queue'); 123 | } 124 | } else { 125 | console.log('clearing question'); 126 | socket.emit('clear question'); 127 | $("#question-input").prop('disabled', false); 128 | $('#question-input').attr('placeholder', 'ask a question'); 129 | $('#question-submit i').html('send'); 130 | $('#question-submit').attr('title', ''); 131 | socket.emit('get queue'); 132 | } 133 | }); 134 | $('#answer-submit').click(function() { 135 | if (/\S/.test($('#answer-input').val())) { 136 | socket.emit('submit answer', $('#answer-input').val()); 137 | } 138 | $('#answer-input').val(''); 139 | }); 140 | 141 | socket.on('client disconnect', function(data) { 142 | $('p[answer-id="' + data + '"]').parent().parent().parent().remove(); 143 | socket.emit('get queue'); 144 | }); 145 | 146 | 147 | 148 | }); 149 | function upvote(id, number) { 150 | console.log('upvote clicked'); 151 | socket.emit('upvote', {id:id, number:number}); 152 | $('.upvote[number="' + number + '"][answer-id="' + id + '"]').toggleClass('upvote-clicked'); 153 | } 154 | 155 | function sortAnswers() { //http://jsfiddle.net/MikeGrace/Vgavb/ 156 | 157 | // get array of elements 158 | var myArray = $("#answer-list li"); 159 | var count = 0; 160 | 161 | // sort based on timestamp attribute 162 | myArray.sort(function (a, b) { 163 | 164 | // convert to integers from strings 165 | a = parseInt($(a).attr("score"), 10); 166 | b = parseInt($(b).attr("score"), 10); 167 | // compare 168 | return b-a; 169 | }); 170 | 171 | console.log(myArray); 172 | 173 | // put sorted results back on page 174 | $("#answer-list ").append(myArray); 175 | } -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var app = express(); 3 | var http = require('http').Server(app); 4 | var path = require('path'); 5 | var socket = require('socket.io')(http); 6 | var favicon = require('serve-favicon'); 7 | var escape = require('escape-html'); 8 | 9 | app.set('view engine', 'jade'); 10 | app.set('views', __dirname + "/views"); 11 | app.use(express.static(path.join(__dirname, 'public'))); 12 | //app.use(favicon(path.join(__dirname,'public','favicon.ico'))); 13 | app.enable('trust proxy'); 14 | 15 | var clients = {}; 16 | var questions = {}; 17 | var answers = {}; 18 | var questionDuration = 41; 19 | var secondsLeft = 0; 20 | var currentQuestion = {question: 'no questions, why don\'t you start us off and create a new one?', id: 'n/a'}; 21 | var maxAnswers = 3; 22 | var maxTextLength = 400; 23 | var answersInSecond = {}; 24 | var mutedIps = {}; 25 | var maxAnswersPerSecond = 3; 26 | var muteLength = 60; 27 | 28 | app.get('/', function(req, res){ 29 | res.render('index', {ip: req.headers['x-forwarded-for'] || req.connection.remoteAddress}); 30 | }); 31 | 32 | socket.on('connection', function(client) { 33 | 34 | client.on('join', function() { 35 | console.log('User ' + client.id + " connected") 36 | clients[client.id] = {upvoted: [], ip: client.request.connection.remoteAddress, muted: false}; 37 | console.log(client); //http://stackoverflow.com/questions/10849687/express-js-how-to-get-remote-client-address 38 | client.emit('join', {id: client.id, answers: answers, question: currentQuestion}); 39 | socket.emit('clients online', Object.keys(clients).length); 40 | }); 41 | 42 | client.on('disconnect', function() { 43 | console.log('User ' + client.id + " disconnected") 44 | 45 | //deletes: questions, answers, upvotes 46 | try { 47 | delete questions[client.id]; 48 | var upvotedId; 49 | var upvotedNumber; 50 | for (i in clients[client.id].upvoted) { 51 | upvotedId = clients[client.id].upvoted[i].id; 52 | upvotedNumber = clients[client.id].upvoted[i].number; 53 | 54 | answers[upvotedId][upvotedNumber].score--; 55 | socket.emit('upvote', answers[upvotedId][upvotedNumber]); 56 | } 57 | 58 | delete answers[client.id]; 59 | socket.emit('client disconnect', client.id); 60 | 61 | } catch (err) { 62 | console.log('error on disconnect (delete question/answers): ' + err); 63 | } 64 | 65 | //remove client 66 | try { 67 | delete clients[client.id]; 68 | socket.emit('clients online', Object.keys(clients).length); 69 | } catch (err) { 70 | console.log('error on disconnect (remove client): ' + err); 71 | } 72 | }); 73 | 74 | client.on('submit question', function(data) { 75 | try { 76 | data = escape(data); 77 | if (data.length > maxTextLength) { 78 | data = data.substring(0, maxTextLength); 79 | } 80 | console.log('question received: ' + data); 81 | if (client.id in questions) { 82 | console.log('smartass bypassed the frontend to try to add multiple questions'); 83 | } else if (/\S/.test(data)) { //checks for empty/whitespace 84 | questions[client.id] = {question: data, id: client.id}; 85 | socket.emit('new question entered', Object.keys(questions).length); 86 | if (Object.keys(questions).length == 1 && currentQuestion.id == 'n/a') { 87 | newQuestion(); 88 | } 89 | } 90 | } catch (err) { 91 | console.log('error on submit question: ' + err); 92 | } 93 | }); 94 | 95 | client.on('clear question', function() { 96 | delete questions[client.id]; 97 | }); 98 | 99 | client.on('submit answer', function(data) { 100 | try { 101 | data = escape(data); 102 | if (data.length > maxTextLength) { 103 | data = data.substring(0, maxTextLength); 104 | } 105 | console.log('answer received: ' + data); 106 | if (typeof answers[client.id] == "undefined") { 107 | answers[client.id] = []; 108 | } 109 | if (typeof mutedIps[clients[client.id].ip] == 'undefined') { //is the ip not muted? 110 | if (answers[client.id].length < maxAnswers && /\S/.test(data)) { //checks for empty/whitespace 111 | 112 | //adds answer to hash that lasts 1 second 113 | if (typeof answersInSecond[clients[client.id].ip] == "undefined") { 114 | answersInSecond[clients[client.id].ip] = 1; 115 | } else { 116 | answersInSecond[clients[client.id].ip] ++; 117 | } 118 | 119 | if (answersInSecond[clients[client.id].ip] > maxAnswersPerSecond) { //if more than x answers per second... 120 | mutedIps[clients[client.id].ip] = muteLength; 121 | console.log('ip ' + clients[client.id].ip + ' muted for ' + muteLength + ' seconds'); 122 | 123 | //delete muted IP after mutelength 124 | unmute(clients[client.id].ip); 125 | } 126 | 127 | 128 | answers[client.id].push({answer: data, id: client.id, score: 0, number:answers[client.id].length}); 129 | socket.emit('new answer', answers[client.id][answers[client.id].length - 1]); 130 | if (answers[client.id].length == maxAnswers) { 131 | client.emit('max answers'); 132 | } 133 | 134 | 135 | } 136 | } else { 137 | console.log('ip ' + mutedIps[clients[client.id].ip] + ' is muted, no answer sent'); 138 | } 139 | } catch (err) { 140 | console.log('error on submit answer: ' + err); 141 | } 142 | 143 | }); 144 | 145 | client.on('get queue', function() { 146 | var place = 0; 147 | for (var key in questions) { 148 | place++; 149 | if (key == client.id) { 150 | client.emit('get queue', {place: place, total: Object.keys(questions).length}); 151 | return; 152 | } 153 | } 154 | //client has no questions, only return length of queue 155 | client.emit('get queue', {place: 'n/a', total: Object.keys(questions).length}); 156 | }); 157 | 158 | client.on('upvote', function(data) { 159 | try { 160 | for (i in clients[client.id].upvoted) { 161 | //checks to see if it's already upvoted 162 | if (clients[client.id].upvoted[i].id == data.id && clients[client.id].upvoted[i].number == data.number) { 163 | clients[client.id].upvoted.splice(i, 1); 164 | answers[data.id][data.number].score--; 165 | socket.emit('upvote', answers[data.id][data.number]); 166 | //makes sure it doesn't upvote it back 167 | return; 168 | } 169 | } 170 | //otherwise increment it 171 | answers[data.id][data.number].score++; 172 | socket.emit('upvote', answers[data.id][data.number]); 173 | clients[client.id].upvoted.push({id:data.id, number:data.number}); 174 | 175 | } catch (err) { 176 | console.log('error on upvote: ' + err); 177 | } 178 | }); 179 | 180 | }); 181 | 182 | var port = process.env.PORT || 3000; 183 | http.listen(port, function(){ 184 | console.log('listening on port: ' + port); 185 | }); 186 | 187 | function newQuestion() { 188 | answers = {}; 189 | for (key in clients) { 190 | clients[key].upvoted = []; 191 | } 192 | if (Object.keys(questions).length != 0) { 193 | socket.emit('new question sent', questions[Object.keys(questions)[0]]); 194 | currentQuestion = questions[Object.keys(questions)[0]]; //transfers from queue 195 | delete questions[Object.keys(questions)[0]]; //deletes first in queue 196 | 197 | //start timer 198 | secondsLeft = questionDuration; 199 | socket.emit('timer', {secondsLeft: secondsLeft, questionDuration: questionDuration}); 200 | timer(); 201 | } else { //no questions 202 | console.log('No questions in queue...'); 203 | currentQuestion = {question: 'no questions, why don\'t you start us off and create a new one?', id: 'n/a'}; 204 | socket.emit('new question sent', {question: 'no questions, why don\'t you start us off and create a new one?', id:'n/a'}) 205 | } 206 | } 207 | 208 | function unmute(ip) { //unmutes ip after mutelength seconds 209 | setTimeout(function() { 210 | try { 211 | delete mutedIps[ip]; 212 | console.log('ip ' + ip + ' unmuted'); 213 | } catch (err) { 214 | console.log('error in unmuting IP: ' + err); 215 | } 216 | }, muteLength*1000); 217 | } 218 | 219 | //loops every second and emits timer data to client 220 | function timer() { 221 | setTimeout(function() { 222 | secondsLeft -= 1; 223 | answersInSecond = {}; //makes sure this is empty every second 224 | 225 | if (secondsLeft >= 0) { 226 | socket.emit('timer', {secondsLeft: secondsLeft, questionDuration: questionDuration}); 227 | timer(); 228 | } else { 229 | newQuestion(); 230 | } 231 | }, 1000) 232 | } 233 | 234 | -------------------------------------------------------------------------------- /views/index.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | meta(name="viewport", content="width=device-width") 5 | link(href="https://fonts.googleapis.com/icon?family=Material+Icons", rel="stylesheet") 6 | link(rel='stylesheet', href='/css/style.css') 7 | script(src='/socket.io/socket.io.js') 8 | script(src='https://code.jquery.com/jquery-2.2.2.min.js',integrity='sha256-36cp2Co+/62rEAAYHLmRCPIych47CvdM+uTBJwSzWjI=', crossorigin='anonymous') 9 | script(src='/js/script.js') 10 | title rapidpoll 11 | body 12 | #container 13 | h1 rapidpoll 14 | 15 | #question-div 16 | 17 | h2#question loading... 18 | #timer 19 | 20 | #input-container 21 | .input-div 22 | input#answer-input(type='text', disabled, placeholder='no question to answer') 23 | .submit-div 24 | a#answer-submit 25 | i.material-icons send 26 | 27 | .input-div 28 | input#question-input(type='text', placeholder='ask a question') 29 | .submit-div 30 | a#question-submit 31 | i.material-icons send 32 | 33 | #info 34 | p#online 35 | p#queue 36 | 37 | p#ip=ip 38 | 39 | 40 | 41 | 42 | #answer-list 43 | ul 44 | 45 | p#links 46 | a(href='https://github.com/williamyeny/rapidpoll',target='_blank') 47 | i.material-icons code 48 | a(onclick='document.getElementById(\'paypal\').submit();') 49 | i.material-icons attach_money 50 |
    51 | 52 | 54 | 55 |
    56 | 57 | #ad 58 | script(async, src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js") 59 | ins.adsbygoogle(style="display:block", data-ad-client="ca-pub-6411505887325343", data-ad-slot="9725860914", data-ad-format="auto") 60 | 61 | script (adsbygoogle = window.adsbygoogle || []).push({}); --------------------------------------------------------------------------------