├── .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 "
";
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 |
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({});
--------------------------------------------------------------------------------