// Add a parameter to the query string function insertParam(key, value) { key = encodeURIComponent(key); value = encodeURIComponent(value); var kvp = window.location.search.substr(1).split('&'); var i = kvp.length, x; while (i--) { x = kvp[i].split('='); if (x[0] == key) { x[1] = value; kvp[i] = x.join('='); break; } } if (i<0) kvp[kvp.length] = [key,value].join('='); window.location.search = kvp.join('&'); } // For now we'll just remove nested quotes from the parent post function quotify(id) { $('#quote-' + id).attr('disabled', true); var html = $('#message-' + id).clone(); html.find('blockquote').remove(); $('#replytext-' + id).val( html.text().replace(/\n\s*\n\s*\n/g, '\n\n').split(/\r?\n/).map( function (line) { return ("> " + line); } ).join('\n') + $('#replytext-' +id).val() ); } // (Try to) post a new message to 'sub' via the web API async function postNew(sub) { $('#newmessage-button').attr('disabled', true); var to = $('#newmessage-to').val(); var subject = $('#newmessage-subject').val(); var body = $('#newmessage-body').val(); const data = await v4_post('./api/forum.ssjs', { call: 'post', sub, to, subject, body }); if (data.success) { $('#newmessage').remove(); insertParam('notice', 'Your message has been posted.'); } $('#newmessage-button').attr('disabled', false); } // (Try to) post a reply to message number 'id' of 'sub' via the web API async function postReply(sub, id) { $('#reply-button-' + id).attr('disabled', true); var body = $('#replytext-' + id).val(); const data = await v4_post('./api/forum.ssjs', { call: 'post-reply', sub, body, pid: id }); if (data.success) { $('#quote-' + id).attr('disabled', false); $('#replybox-' + id).remove(); insertParam('notice', 'Your message has been posted.'); } else { $('#reply-button-' + id).attr('disabled', false); } } // (Try to) delete a message via the web API async function deleteMessage(sub, id) { const res = await v4_post('./api/forum.ssjs', { call: 'delete-message', sub: sub, number: id }); if (res.success) { $('#li-' + id).remove(); insertParam('notice', 'Message deleted.'); } } // Add a new message input form to the element with id 'forum-list-container' for sub 'sub' function addNew(sub) { if ($('#newmessage').length > 0) return; $('#forum-list-container').append( '<li id="newmessage" class="list-group-item">' + '<input id="newmessage-to" class="form-control" type="text" placeholder="To"><br>' + '<input id="newmessage-subject" class="form-control" type="text" placeholder="Subject"><br>' + '<textarea id="newmessage-body" class="form-control" rows="8"></textarea><br>' + '<input id="newmessage-button" class="btn btn-primary" type="submit" value="Submit" onclick="postNew(\'' + sub + '\')">' + '</li>' ); v4_get('./api/forum.ssjs?call=get-signature').then(data => { $('#newmessage-body').val($('#newmessage-body').val() + '\r\n' + data.signature); $("#newmessage-body")[0].setSelectionRange(0,0); }); window.location.hash = '#newmessage'; $('#newmessage-body').keydown(evt => evt.stopImmediatePropagation()); } async function submitPoll(sub) { $('#newpoll-submit').attr('disabled', true); if ($('input[name="newpoll-answers"]:checked').length !== 1) return; var subject = $('#newpoll-subject').val(); if (subject.length < 1) return; var answerCount = $('input[name="newpoll-answers"]:checked:first').val(); if (answerCount == 2) answerCount = $('input[name="newpoll-answer-count"]').val(); if (answerCount < 0 || answerCount > 15) return; var results = parseInt($('input[name="newpoll-results"]:checked').val()); if (results < 0 || results > 3) return; var answers = []; $('input[name="newpoll-answer-input"]').each(function () { var val = $(this).val(); if (val !== '') answers.push(val); }); if (answers.length < 1) return; var comments = []; $('input[name="newpoll-comment-input"]').each(function () { var val = $(this).val(); if (val !== '') comments.push(val); }); const post_data = { sub, subject, votes: answerCount, results, answer: answers }; if (comments.length) post_data.comment = comments; const res = await v4_post('./api/forum.ssjs?call=submit-poll', post_data); $('#newpoll-submit').attr('disabled', false); if (res.success) { $('#newpoll').remove(); insertParam('notice', 'Your poll has been posted.'); } } function addPollField(type, elem) { var prefix = 'newpoll-' + type; var count = $('div[name="' + prefix + '"]').length; if (type === 'answer' && count > 15) return; var number = count + 1; $(elem).append( '<div id="' + prefix + '-container-' + number + '" name="' + prefix + '" class="form-group">' + '<label for="' + prefix + '-' + number + '" class="col-sm-2 control-label">' + (type === 'answer' ? 'Answer' : 'Comment') + '</label>' + '<div class="col-sm-9">' + '<input id="' + prefix + '-' + number + '" class="form-control" name="' + prefix + '-input" type="text" maxlength="70"> ' + '</div>' + '<div class="col-sm-1">' + '<button type="button" class="btn btn-danger" onclick="$(\'#' + prefix + '-container-' + number + '\').remove()">' + '<span class="glyphicon glyphicon-remove"></span>' + '</button> ' + '</div>' + '</div>' ); $('#' + prefix + '-' + number).keydown(function (evt) { evt.stopImmediatePropagation(); }); } function addPoll(sub) { if ($('#newpoll').length > 0) return; $('#forum-list-container').append( '<li id="newpoll" class="list-group-item">' + '<strong>Add a new poll</strong>' + '<form id="newpoll-form" class="form-horizontal">' + '<div class="form-group">' + '<label for="newpoll-subject" class="col-sm-2 control-label">Question</label>' + '<div class="col-sm-10">' + '<input id="newpoll-subject" class="form-control" type="text" placeholder="Required" maxlength="70">' + '</div>' + '</div>' + '<div id="newpoll-comment-group"></div>' + '<div class="form-group">' + '<label for="newpoll-answers" class="col-sm-2 control-label">Selection</label>' + '<div class="col-sm-10">' + '<label class="radio-inline">' + '<input type="radio" name="newpoll-answers" value="1" checked> Single' + '</label>' + '<label class="radio-inline">' + '<input type="radio" name="newpoll-answers" value="2"> Multiple ' + '<input type="number" name="newpoll-answer-count" min="1" max="15" value="1">' + '</label>' + '</div>' + '</div>' + '<div class="form-group">' + '<label for="newpoll-results" class="col-sm-2 control-label">Show results</label>' + '<div class="col-sm-10">' + '<label class="radio-inline">' + '<input type="radio" name="newpoll-results" value="0" checked> Voters' + '</label>' + '<label class="radio-inline">' + '<input type="radio" name="newpoll-results" value="1"> Everyone' + '</label>' + '<label class="radio-inline">' + '<input type="radio" name="newpoll-results" value="2"> Me Only (Until closed) ' + '</label>' + '<label class="radio-inline">' + '<input type="radio" name="newpoll-results" value="3"> Me Only ' + '</label>' + '</div>' + '</div>' + '<div id="newpoll-answer-group"></div>' + '<div id="newpoll-button" class="form-group">' + '<div class="col-sm-offset-2 col-sm-10">' + '<button id="newpoll-submit" type="button" class="btn btn-primary" onclick="submitPoll(\'' + sub + '\')">' + 'Submit' + '</button>' + '<div class="pull-right">' + '<button type="button" title="Add another comment" class="btn btn-success" onclick="addPollField(\'comment\', \'#newpoll-comment-group\')">' + '<span class="glyphicon glyphicon-pencil"></span>' + '</button> ' + '<button type="button" title="Add another answer" class="btn btn-success" onclick="addPollField(\'answer\', \'#newpoll-answer-group\')">' + '<span class="glyphicon glyphicon-plus"></span>' + '</button> ' + '</div>' + '</div>' + '</div>' + '</form>' + '</li>' ); addPollField('comment', '#newpoll-comment-group'); addPollField('answer', '#newpoll-answer-group'); addPollField('answer', '#newpoll-answer-group'); window.location.hash = '#newpoll'; } // Add a reply input form to the page for message with number 'id' in sub 'sub' function addReply(sub, id) { if ($('#replybox-' + id).length > 0) return; $('#li-' + id).append( '<div class="reply" id="replybox-' + id + '">' + '<strong>Reply</strong>' + '<textarea rows="8" class="form-control reply" id="replytext-' + id + '""></textarea>' + '<button id="quote-' + id + '" class="btn" onclick="quotify(' + id + ')">Quote</button> ' + '<input id="reply-button-' + id + '" class="btn btn-primary" type="submit" value="Submit" onclick="postReply(\'' + sub + '\', ' + id + ')">' + '</div>' ); v4_get('./api/forum.ssjs?call=get-signature').then(data => { $('#replytext-' + id).val($('#replytext-' + id).val() + '\r\n' + data.signature); $('#replytext-' + id)[0].setSelectionRange(0,0); }); $('#replytext-' + id).keydown(evt => evt.stopImmediatePropagation()); } function onSubUnreadCount(data) { for (sub in data) { if (data[sub].scanned > 0) { $('#badge-' + sub).text(data[sub].total); } else if (data[sub].total > 0) { $('#badge-' + sub).text(data[sub].total); } else { $('#badge-' + sub).text(''); } } } // 'sub' can be a single sub code, or a string of <sub1>&sub=<sub2>&sub=<sub3>... async function getSubUnreadCount(sub) { const res = await v4_get('./api/forum.ssjs?call=get-sub-unread-count&sub=' + sub); onSubUnreadCount(res); } function onGroupUnreadCount(data) { for (group in data) { $('#badge-scanned-' + group).text( (data[group].scanned == 0 ? "" : data[group].scanned) ); $('#badge-ignored-' + group).text( data[group].total == 0 || data[group].total == data[group].scanned ? '' : (data[group].total - data[group].scanned) ); } } // 'group' can be a single group index, or a string of 0&group=1&group=2... async function getGroupUnreadCount(group) { const res = await v4_get('./api/forum.ssjs?call=get-group-unread-count&group=' + group); onGroupUnreadCount(res); } function onThreadStats(data) { Object.keys(data).forEach(e => { let div1; if (!$('#replies-' + e).length) { div1 = $('#forum-thread-replies-template').clone(); div1.attr('id', 'replies-' + e); $('#left-' + e).append(div1); } else { div1 = $('#replies-' + e); } if (data[e].total > 1) { div1.find('strong[data-message-count]').first().html(data[e].total - 1); if (data[e].total == 2) { div1.find('span[data-suffix-reply]').first().attr('hidden', false); } else { div1.find('span[data-suffix-replies]').first().attr('hidden', false); } div1.find('strong[data-last-from]').first().html(data[e].newest.from); div1.find('span[data-last-time]').first().html(data[e].newest.date); div1.attr('hidden', false); } let div2; if (!$('#stats-' + e).length) { div2 = $('#forum-thread-stats-template').clone(); div2.attr('id', 'stats-' + e); $('#right-' + e).append(div2); } else { div2 = $('#stats-' + e); } if (data[e].unread) { const urm = div2.find('span[data-unread-messages]'); if (urm.length) { urm.first().html(data[e].unread); urm.first().attr('hidden', false); div2.attr('hidden', false); } } if (data[e].votes.total) { if (data[e].votes.up.t) { div2.find('span[data-upvotes]').first().html(data[e].votes.up.p + '/' + data[e].votes.up.t); div2.find('span[data-upvotes-badge]').first().css('display', ''); } if (data[e].votes.down.t) { div2.find('span[data-downvotes]').first().html(data[e].votes.down.p + '/' + data[e].votes.down.t); div2.find('span[data-downvotes-badge]').first().css('display', ''); } div2.attr('hidden', false); } }); } /* Fetch a private mail message's body (with links to attachments) where 'id' is the message number. Output it to an element with id 'message-<id>'. */ async function getMailBody(id) { const tgt = `#message-${id}`; if (!$(tgt).attr('hidden')) { $(tgt).attr('hidden', true); } else if ($(tgt).html() != '') { $(tgt).attr('hidden', false); } else { const data = await v4_get(`./api/forum.ssjs?call=get-mail-body&number=${id}`); var str = data.body; if (data.inlines && data.inlines.length > 0) { str += `<br>Inline attachments: ${data.inlines.join('<br>')}<br>`; } if (data.attachments && data.attachments.length > 0) { str += `<br>Attachments: ${data.attachments.join('<br>')}<br>`; } str += '<button class="btn btn-default icon" ' + 'aria-label="Reply to this message" ' + 'title="Reply to this message" ' + 'name="reply-' + id + '" ' + 'onclick="addReply(\'mail\',' + id + ')">' + '<span class="glyphicon glyphicon-comment"></span>' + '</button>' + '<button class="btn btn-default icon" aria-label="Delete this message" ' + 'title="Delete this message" onclick="deleteMessage(\'mail\',' + id + ')">' + '<span class="glyphicon glyphicon-trash"></span>' + '</button>'; if (data.buttons) str += data.buttons.join(''); $(tgt).html(str); $(tgt).attr('hidden', false); } } async function blockSender(id, from, from_net) { const data = await v4_get(`./api/forum.ssjs?call=block-sender&from=${from}&from_net=${from_net}`); if (!data.err) $(`#bsb-${id}`).attr('disabled', true); } async function setScanCfg(sub, cfg) { var opts = [ 'scan-cfg-off', 'scan-cfg-new', 'scan-cfg-youonly' ]; const data = await v4_get('./api/forum.ssjs?call=set-scan-cfg&sub=' + sub + '&cfg=' + cfg); if (!data.success) return; opts.forEach((e, i) => { $('#' + e).toggleClass('btn-primary', (cfg == i)); $('#' + e).toggleClass('btn-default', (cfg != i)); }); } function threadNav() { if (window.location.hash === '') { $($('#forum-list-container').children('.list-group-item')[0]).addClass('current'); } else if ($('#li-' + window.location.hash.substr(1)).length > 0) { $('#li-' + window.location.hash.substr(1)).addClass('current'); } $(window).keydown(function (evt) { var cid = $($('#forum-list-container').children('.current')[0]).attr('id').substr(3); switch (evt.keyCode) { case 37: // Left window.location.hash = $('#pm-' + cid).attr('href'); break; case 39: // Right window.location.hash = $('#nm-' + cid).attr('href'); break; default: break; } }); $(window).on('hashchange', function () { $('#forum-list-container').children('.current').removeClass('current'); var id = window.location.hash.substr(1); if ($('#li-' + id).length < 1) return; $('#li-' + id).addClass('current'); }); } async function vote(sub, id) { id = id.split('-'); if (id.length != 2 || (id[0] != 'uv' && id[0] != 'dv') || isNaN(parseInt(id[1]))) { return; } const data = await v4_get('./api/forum.ssjs?call=vote&sub=' + sub + '&id=' + id[1] + '&up=' + (id[0] === 'uv' ? 1 : 0)); if (!data.success) return; $('#' + id[0] + '-' + id[1]).addClass(id[0] === 'uv' ? 'upvote-fg' : 'downvote-fg'); $('#' + id[0] + '-' + id[1]).attr('disabled', true); $('#' + id[0] + '-' + id[1]).blur(); const count = parseInt($('#' + id[0] + '-count-' + id[1]).text()) + 1; $('#' + id[0] + '-count-' + id[1]).text(count); } function enableVoteButtonHandlers(sub) { $('.btn-uv').click(function () { vote(sub, this.id); }); $('.btn-dv').click(function () { vote(sub, this.id); }); } async function getVotesInThread(sub, id) { const data = await v4_get('./api/forum.ssjs?call=get-thread-votes&sub=' + sub + '&id=' + id); Object.keys(data.m).forEach(m => { var uv = parseInt($('#uv-count-' + m).text()); var dv = parseInt($('#dv-count-' + m).text()); if (uv !== data.m[m].u) { $('#uv-count-' + m).text(data.m[m].u); $('#uv-' + m).addClass('indicator'); } if (dv !== data.m[m].d) { $('#dv-count-' + m).text(data.m[m].d); $('#dv-' + m).addClass('indicator'); } switch (data.m[m].v) { case 1: $('#uv-' + m).addClass('upvote-fg'); $('#uv-' + m).attr('disabled', true); $('#dv-' + m).attr('disabled', true); break; case 2: $('#dv-' + m).addClass('downvote-fg'); $('#uv-' + m).attr('disabled', true); $('#dv-' + m).attr('disabled', true); break; default: break; } }); } async function getVotesInThreads(sub) { const data = await v4_get('./api/forum.ssjs?call=get-sub-votes&sub=' + sub); Object.keys(data).forEach(t => { var uv = data[t].p.u + ' / ' + data[t].t.u; var dv = data[t].p.d + ' / ' + data[t].t.d; if (uv !== $('#uv-count-' + t).text()) $('#uv-count-' + t).text(uv); if (dv !== $('#dv-count-' + t).text()) $('#dv-count-' + t).text(dv); }); } async function submitPollAnswers(sub, id) { if ($('input[name="poll-' + id + '"]:checked').length < 1) return; var answers = []; $('input[name="poll-' + id + '"]:checked').each(function () { answers.push($(this).val()); }); answers = answers.join('&answer='); const post_data = { call: 'submit-poll-answers', sub, id, answer: answers }; const data = await v4_post('./api/forum.ssjs', post_data); $('input[name="poll-' + id + '"]').each(function () { $(this).attr('disabled', true); if ($(this).prop('checked')) { $(this).parent().parent().addClass('upvote-bg'); } }); $('submit-poll-' + id).attr('disabled', true); } function pollControl(id, count) { $('input[name="poll-' + id + '"]').each(function () { $(this).change(function () { if ($('input[name="poll-' + id + '"]:checked').length >= count) { $('input[name="poll-' + id + '"]:not(:checked)').each(function () { $(this).attr('disabled', true); }); } else { $('input[name="poll-' + id + '"]:not(:checked)').each(function () { $(this).attr('disabled', false); }); } }); }); } async function getPollData(sub, id) { const data = await v4_get('./api/forum.ssjs?call=get-poll-results&sub=' + sub + '&id=' + id); data.tally.forEach((e, i) => { if (e > 0) $('#poll-count-' + id + '-' + i).text(e); }); if (data.answers > 0) { $('input[name="poll-' + id + '"]').each(function () { $(this).attr('disabled', true); }); $('#submit-poll-' + id).attr('disabled', true); } }