app/assets/javascripts/octobox.js
var Octobox = (function() {
var maybeConfirm = function(message){
if($('body.disable_confirmations').length){
return true
} else {
return confirm(message);
}
}
var checkSelectAll = function() {
$(".js-select_all").click();
};
var updatePinnedSearchCounts = function(pinned_search) {
var pinned_search = $(pinned_search);
$.get(pinned_search.data('url'), function(data) {
pinned_search.html(data.count);
}).fail(function() {
pinned_search.remove(); // Remove the total value if there's an error
});
}
var updateAllPinnedSearchCounts = function(){
$("span.pinned-search-count").each(function() {
updatePinnedSearchCounts(this);
});
}
var moveCursorToClickedRow = function(event) {
// Don't event.preventDefault(), since we want the
// normal clicking behavior for links, starring, etc
var oldCurrent = getCurrentRow();
var target = $(event.target);
setRowCurrent(oldCurrent, false);
setRowCurrent(target, true);
};
var updateFavicon = function () {
$.get( "/notifications/unread_count", function(data) {
setFavicon(data.count)
});
};
var setFavicon = function(count) {
if (count !== unread_count) {
unread_count = count;
var title = "Octobox";
if (unread_count > 0) {
title += " (" + unread_count + ")";
}
window.document.title = title;
var old_link = document.getElementById("favicon-count");
if ( old_link ) {
$(old_link).remove();
}
var canvas = document.createElement("canvas"),
ctx,
img = document.createElement("img"),
link = document.getElementById("favicon").cloneNode(true),
txt = unread_count + "";
link.id = "favicon-count";
if (canvas.getContext) {
canvas.height = canvas.width = 32;
ctx = canvas.getContext("2d");
img.onload = function () {
ctx.drawImage(this, 0, 0);
if (unread_count > 0){
ctx.fillStyle = "#f93e00";
ctx.font = "bold 20px 'helvetica', sans-serif";
var width = ctx.measureText(txt).width;
ctx.fillRect(0, 0, width+4, 24);
ctx.fillStyle = "#fff";
ctx.fillText(txt, 2, 20);
}
link.href = canvas.toDataURL("image/png");
document.body.appendChild(link);
};
img.src = "/favicon-32x32.png";
}
}
}
var enableTooltips = function() {
if(!("ontouchstart" in window))
{
$("[data-toggle='tooltip']").tooltip();
}
};
var enablePopOvers = function() {
var showTimer;
$('[data-toggle="popover"]').popover({ trigger: "manual" , html: true})
.on("mouseenter", function () {
if (showTimer) {
clearTimeout(showTimer);
}
var _this = this;
showTimer = setTimeout(function () {
showTimer = undefined
$(_this).popover("show");
$(".popover").on("mouseleave", function () {
$(_this).popover('hide');
});
}, 500);
}).on("mouseleave", function () {
if (showTimer) {
clearTimeout(showTimer);
return;
}
var _this = this;
setTimeout(function () {
if (!$(".popover:hover").length) {
$(_this).popover("hide");
}
}, 300);
});
}
var enableKeyboardShortcuts = function() {
// Add shortcut events only once
if (window.row_index !== undefined) return;
window.row_index = 1;
window.current_id = undefined;
$(document).keydown(function(e) {
// disable shortcuts for the search and comment
if ($("#help-box").length && !["search-box","comment_body"].includes(e.target.id) && !e.ctrlKey && !e.metaKey) {
var shortcutFunction = (!e.shiftKey ? shortcuts : shiftShortcuts)[e.which] ;
if (shortcutFunction) { shortcutFunction(e) }
return;
}
// escape search and comment
if(["search-box", "comment_body"].includes(e.target.id) && e.which === 27) shortcuts[27](e);
// post comment form on CMD-enter
if(["comment_body"].includes(e.target.id) && (e.metaKey || e.ctrlKey) && e.which == 13) $('#reply').submit();
});
};
var checkAll = function() {
var checked = $(".js-select_all").prop("checked")
getDisplayedRows().find("input").prop("checked", checked).trigger("change");
};
var muteThread = function() {
var id = $('#notification-thread').data('id');
mute(id);
} ;
var muteSelected = function() {
if (getDisplayedRows().length === 0) return;
if ( $(".js-table-notifications tr").length === 0 ) return;
var ids = getIdsFromRows(getMarkedOrCurrentRows());
mute(ids);
};
var mute = function(ids){
var result = maybeConfirm("Are you sure you want to mute?");
if (result) {
$.post( "/notifications/mute_selected" + location.search, { "id[]": ids})
.done(function() {
resetCursorAfterRowsRemoved(ids);
updateFavicon();
})
.fail(function(){
notify("Could not mute notification(s)", "danger");
});
}
};
var markReadSelected = function() {
if (getDisplayedRows().length === 0) return;
var rows = getMarkedOrCurrentRows();
rows.addClass("blur-action");
$.post("/notifications/mark_read_selected" + location.search, {"id[]": getIdsFromRows(rows)})
.done(function () {
rows.removeClass("blur-action");
rows.removeClass("active");
updateFavicon();
})
.fail(function(){
notify("Could not mark notification(s) read", "danger");
});
};
var toggleArchive = function() {
if ($(".archive_toggle").hasClass("archive_selected")) {
archiveSelected()
} else {
unarchiveSelected()
}
};
var archiveSelected = function(){
if (getDisplayedRows().length === 0) return;
var ids = getIdsFromRows(getMarkedOrCurrentRows());
archive(ids, true);
}
var unarchiveSelected = function(){
if (getDisplayedRows().length === 0) return;
var ids = getIdsFromRows(getMarkedOrCurrentRows());
archive(ids, false);
}
var archiveThread = function(){
var id = $('#notification-thread').data('id');
archive([id], true);
}
var unarchiveThread = function(){
var id = $('#notification-thread').data('id');
archive([id], false);
}
var archive = function(ids, value){
$.post( "/notifications/archive_selected" + location.search, { "id[]": ids, "value": value } )
.done(function() {
resetCursorAfterRowsRemoved(ids);
updateFavicon();
})
.fail(function(){
notify("Could not archive notification(s)", "danger");
});
}
var toggleSelectAll = function() {
$.map($("button.select_all > span"), function( val, i ) {
$(val).toggleClass("bold")
});
$("button.select_all").toggleClass("all_selected")
};
var refreshOnSync = function() {
if(!$(".js-sync .octicon").hasClass("spinning")){
$(".js-sync .octicon").addClass("spinning");
}
$.ajax({"url": "/notifications/syncing.json", data: {}, error: function(xhr, status) {
setTimeout(refreshOnSync, 2000)
}, success: function(data, status, xhr) {
if (data["error"] != null) {
$(".sync .octicon").removeClass("spinning");
notify(data["error"], "danger")
} else {
Turbolinks.visit("/"+location.search);
}
}
});
};
var sync = function() {
if($("a.js-sync.js-async").length) {
$.get("/notifications/sync.json", refreshOnSync);
} else {
if(!$(".js-sync .octicon").hasClass("spinning")){
$(".js-sync .octicon").addClass("spinning");
}
Turbolinks.visit($("a.js-sync").attr("href"))
$(".sync .octicon").removeClass("spinning");
}
};
var setAutoSyncTimer = function() {
var refresh_interval = $(".js-table-notifications").data("refresh-interval");
if (isNaN(refresh_interval)) return;
refresh_interval > 0 && setInterval(autoSync, refresh_interval)
};
var recoverPreviousCursorPosition = function() {
if ( current_id === undefined ) {
row_index = Math.min(row_index, $(".js-table-notifications tr").length);
row_index = Math.max(row_index, 1);
} else {
row_index = $("#notification-"+current_id).index() + 1;
current_id = undefined;
}
$(".js-table-notifications tbody tr:nth-child(" + row_index + ")").first().find("td").first().addClass("current js-current");
}
var markRowCurrent = function(row) {
// Clicking a row marks it current
$(".current.js-current").removeClass("current js-current");
row.find("td").first().addClass("current js-current");
};
var initShiftClickCheckboxes = function() {
// handle shift+click multiple check
var notificationCheckboxes = $(".notification-checkbox .custom-checkbox input");
$(".notification-checkbox .custom-checkbox").click(function(e) {
e.preventDefault();
window.getSelection().removeAllRanges(); // remove all text selected
if(!lastCheckedNotification) {
// No notifications selected
lastCheckedNotification = $(this).find("input");
lastCheckedNotification.prop("checked", !lastCheckedNotification.prop("checked")).trigger('change');
return;
}
if(e.shiftKey) {
var start = notificationCheckboxes.index($(this).find("input"));
var end = notificationCheckboxes.index(lastCheckedNotification);
var selected = notificationCheckboxes.slice(Math.min(start,end), Math.max(start,end) + 1)
selected.prop("checked", lastCheckedNotification.prop("checked")).trigger('change');
lastCheckedNotification = $(this).find("input");
return;
}
lastCheckedNotification = $(this).find("input");
lastCheckedNotification.prop("checked", !lastCheckedNotification.prop("checked")).trigger('change');
});
};
var toggleStarClick = function(row) {
star(row.data("id"))
};
var star = function(id){
$('#notification-thread').data('id') == id ? $('#thread').find('.toggle-star').toggleClass("star-active star-inactive") : null;
var fill_star_path = '<path fill-rule="evenodd" d="M8 .25a.75.75 0 01.673.418l1.882 3.815 4.21.612a.75.75 0 01.416 1.279l-3.046 2.97.719 4.192a.75.75 0 01-1.088.791L8 12.347l-3.766 1.98a.75.75 0 01-1.088-.79l.72-4.194L.818 6.374a.75.75 0 01.416-1.28l4.21-.611L7.327.668A.75.75 0 018 .25z"></path>'
var empty_star_path = '<path fill-rule="evenodd" d="M8 .25a.75.75 0 01.673.418l1.882 3.815 4.21.612a.75.75 0 01.416 1.279l-3.046 2.97.719 4.192a.75.75 0 01-1.088.791L8 12.347l-3.766 1.98a.75.75 0 01-1.088-.79l.72-4.194L.818 6.374a.75.75 0 01.416-1.28l4.21-.611L7.327.668A.75.75 0 018 .25zm0 2.445L6.615 5.5a.75.75 0 01-.564.41l-3.097.45 2.24 2.184a.75.75 0 01.216.664l-.528 3.084 2.769-1.456a.75.75 0 01.698 0l2.77 1.456-.53-3.084a.75.75 0 01.216-.664l2.24-2.183-3.096-.45a.75.75 0 01-.564-.41L8 2.694v.001z"></path>'
var svg = $("#notification-"+id).find(".toggle-star")
svg.toggleClass("star-active star-inactive");
svg.toggleClass("octicon-star octicon-star-fill")
svg.html(svg.hasClass('star-active') ? fill_star_path : empty_star_path)
$.post("/notifications/"+id+"/star")
.fail(function(){
$('#notification-thread').data('id') == id ? $('#thread').find('.toggle-star').toggleClass("star-active star-inactive") : null;
svg.toggleClass("star-active star-inactive");
svg.toggleClass("octicon-star octicon-star-fill")
svg.html(svg.hasClass('star-active') ? fill_star_path : empty_star_path)
notify("Could not toggleeeee star(s)", "danger");
});
};
var changeArchive = function() {
if ( hasMarkedRows() ) {
$("button.archive_selected, button.unarchive_selected, button.mute_selected, button.delete_selected").show().css("display", "inline-block");
if ( !hasMarkedRows(true) ) {
$(".js-select_all").prop("checked", true).prop("indeterminate", false);
$("button.select_all").show().css("display", "inline-block");
} else {
$(".js-select_all").prop("checked", false).prop("indeterminate", true);
$("button.select_all").hide();
}
} else {
$(".js-select_all").prop("checked", false).prop("indeterminate", false);
$("button.archive_selected, button.unarchive_selected, button.mute_selected, button.select_all, button.delete_selected").hide();
}
var marked_unread_length = getMarkedRows().filter(".active").length;
if ( marked_unread_length > 0 ) {
$("button.mark_read_selected").show().css("display", "inline-block");
} else {
$("button.mark_read_selected").hide();
}
};
var removeCurrent = function() {
$("td.js-current").removeClass("current js-current");
};
var closeThread = function() {
history.pushState({thread: $(this).attr('href')}, 'Octobox', $(this).attr('href'))
$("#thread").addClass("d-none");
$(".flex-main").removeClass("show-thread");
};
var toggleOffCanvas = function() {
$(".flex-content").toggleClass("active");
};
function markRead(id) {
$.post("/notifications/mark_read_selected" + location.search, {"id": id})
.done(function() {
updateFavicon();
})
.fail(function(){
notify("Could not mark notification(s) read", "danger");
});
$("#notification-"+id).removeClass("active");
};
function setViewportHeight() {
var vh = window.innerHeight * 0.01;
document.documentElement.style.setProperty('--vh', "".concat(vh, "px"));
};
var initialize = function() {
enableTooltips();
enablePopOvers();
setViewportHeight();
window.addEventListener('resize', setViewportHeight);
if ($("#help-box").length){
enableKeyboardShortcuts();
setFavicon($('.js-unread-count').data('count'));
initShiftClickCheckboxes();
recoverPreviousCursorPosition();
setAutoSyncTimer();
}
// Unread counts for pinned searches
updateAllPinnedSearchCounts();
// Sync Handling
if($(".js-is_syncing").length){ refreshOnSync() }
if($(".js-start_sync").length){ sync() }
if($(".js-initial_sync").length){ sync() }
window.onpopstate = function(event) {
if(event.state.thread){
$('#thread').html($('#loading').html())
$.get(event.state.thread, function(data){
$('#thread').html(data)
});
}
};
};
var deleteNotifications = function(ids){
var result = maybeConfirm("Are you sure you want to delete?");
if (result) {
$.post("/notifications/delete_selected" + location.search, {"id[]": ids})
.done(function() {
resetCursorAfterRowsRemoved(ids);
updateFavicon();
})
.fail(function(){
notify("Could not delete notification", "danger");
});
}
}
var deleteSelected = function(){
if (getDisplayedRows().length === 0) return;
var rows = getMarkedOrCurrentRows();
rows.addClass("blur-action");
var ids = getIdsFromRows(rows);
deleteNotifications(ids);
}
var deleteThread = function() {
var id = $('#notification-thread').data('id');
deleteNotifications(id);
} ;
var viewThread = function() {
history.pushState({thread: $(this).attr('href')}, 'Octobox', $(this).attr('href'))
$('#thread').html($('#loading').html())
$.get($(this).attr('href'), function(data){
if (data["error"] != null) {
notify(data["error"], "danger")
} else {
$('#thread').html(data)
}
});
$("#thread").removeClass("d-none");
$(".flex-main").addClass("show-thread");
$(".flex-content").removeClass("active")
subscribeToComments();
return false;
}
var expandComments = function() {
history.pushState({thread: $(this).attr('href')}, 'Octobox', $(this).attr('href'))
$('#more-comments').html($('#loading').html())
$.get($(this).attr('href'), function(data){
if (data["error"] != null) {
notify(data["error"], "danger")
} else {
$('#more-comments').html(data)
}
});
return false;
}
// private methods
var getDisplayedRows = function() {
return $(".js-table-notifications tr.notification")
};
var getMarkedRows = function(unmarked) {
// gets all marked rows (or unmarked rows if unmarked is true)
return unmarked ? getDisplayedRows().has("input:not(:checked)") : getDisplayedRows().has("input:checked")
};
var getIdsFromRows = function(rows) {
return $("button.select_all").hasClass("all_selected") ?
"all" : $.map(rows, function(row) {return $(row).find("input").val()})
};
var hasMarkedRows = function(unmarked) {
// returns true if there are any marked rows (or unmarked rows if unmarked is true)
return getMarkedRows(unmarked).length > 0
};
var getCurrentRow = function() {
return getDisplayedRows().has("td.js-current");
};
var getMarkedOrCurrentRows = function() {
return hasMarkedRows() ? getMarkedRows() : getCurrentRow()
};
var cursorDown = function() {
moveCursor("up")
};
var cursorUp = function() {
moveCursor("down")
};
var nextPage = function() {
nextPageButton = $(".page-item:last-child .page-link[rel=next]");
if (nextPageButton.length) window.location.href = nextPageButton.attr('href');
}
var prevPage = function() {
previousPageButton = $(".page-item:first-child .page-link[rel=prev]")
if (previousPageButton.length) window.location.href = previousPageButton.attr('href');
}
var markCurrent = function() {
currentRow = getCurrentRow().find("input[type=checkbox]");
$(currentRow).prop("checked", !$(currentRow).prop("checked")).trigger('change');
};
var resetCursorAfterRowsRemoved = function(ids) {
var current = getCurrentRow();
while ( $.inArray(getIdsFromRows(current)[0], ids) > -1 && current.next().length > 0) {
current = current.next();
}
while ( $.inArray(getIdsFromRows(current)[0], ids) > -1 && current.prev().length > 0) {
current = current.prev();
}
window.current_id = getIdsFromRows(current)[0];
Turbolinks.visit("/"+location.search);
};
var toggleStar = function() {
toggleStarClick(getCurrentRow().find(".toggle-star"))
};
var openModal = function() {
$("#help-box").modal({ keyboard: false });
};
var focusSearchInput = function(e) {
e.preventDefault();
$("#search-box").focus();
}
var openCurrentLink = function(e) {
e.preventDefault(e);
getCurrentRow().find("td.notification-subject .link")[0].click();
};
var notify = function(message, type) {
$(".header-flash-messages").remove();
var alert_html = [
"<div class='flex-header header-flash-messages'>",
" <div class='alert alert-" + type + " fade show'>",
" <button class='close' data-dismiss='alert'>x</button>",
message,
" </div>",
"</div>"
].join("\n");
$(".flex-header").after(alert_html);
};
var autoSync = function() {
hasMarkedRows() || sync()
};
var escPressed = function(e) {
if ($("#help-box").is(":visible")) {
$("#help-box").modal("hide");
} else if($(".flex-main").hasClass("show-thread")){
closeThread();
} else if($("#search-box").is(":focus")) {
$(".table-notifications").attr("tabindex", -1).focus();
} else {
clearFilters();
}
};
var clearFilters = function() {
Turbolinks.visit("/");
};
var scrollToCursor = function() {
var current = $("td.js-current");
var table_offset = $(".js-table-notifications").position().top;
var cursor_offset = current.offset().top;
var cursor_relative_offset = current.position().top;
var cursor_height = current.height();
var menu_height = $(".js-octobox-menu").height();
var scroll_top = $(document).scrollTop();
var window_height = $(window).height();
if ( cursor_offset < menu_height + scroll_top ) {
$("html, body").animate({
scrollTop: table_offset + cursor_relative_offset - cursor_height
}, 0);
}
if ( cursor_offset > scroll_top + window_height - cursor_height ) {
$("html, body").animate({
scrollTop: cursor_offset - window_height + 2*cursor_height
}, 0);
}
};
var setRowCurrent = function(row, add) {
var classes = "current js-current";
var td = row.find("td.notification-checkbox");
add ? td.addClass(classes) : td.removeClass(classes);
};
var moveCursor = function(upOrDown) {
var oldCurrent = getCurrentRow();
var target = upOrDown === "up" ? oldCurrent.next() : oldCurrent.prev();
if(target.length > 0) {
setRowCurrent(oldCurrent, false);
setRowCurrent(target, true);
scrollToCursor();
}
};
// keyboard shortcuts when shift key is pressed
var shiftShortcuts = {
191: openModal, // ?
}
var shortcuts = {
65: checkSelectAll, // a
68: markReadSelected, // d
74: cursorDown, // j
75: cursorUp, // k
78: nextPage, // n
80: prevPage, // p
83: toggleStar, // s
88: markCurrent, // x
89: toggleArchive, // y
69: toggleArchive, // e
77: muteSelected, // m
13: openCurrentLink, // Enter
79: openCurrentLink, // o
191: focusSearchInput, // /
190: sync, // .
82: sync, // r
27: escPressed, // esc
51: deleteSelected // #
}
var unread_count = 0;
var lastCheckedNotification = null;
return {
moveCursorToClickedRow: moveCursorToClickedRow,
checkAll: checkAll,
muteThread: muteThread,
muteSelected: muteSelected,
markReadSelected: markReadSelected,
archiveSelected: archiveSelected,
unarchiveSelected: unarchiveSelected,
toggleSelectAll: toggleSelectAll,
sync: sync,
markRowCurrent: markRowCurrent,
closeThread: closeThread,
archiveThread: archiveThread,
unarchiveThread: unarchiveThread,
toggleStarClick: toggleStarClick,
changeArchive: changeArchive,
initialize: initialize,
removeCurrent: removeCurrent,
toggleOffCanvas: toggleOffCanvas,
markRead: markRead,
deleteSelected: deleteSelected,
deleteThread: deleteThread,
viewThread: viewThread,
expandComments: expandComments,
updateAllPinnedSearchCounts: updateAllPinnedSearchCounts
}
})();