mirror of
https://github.com/chylex/Nextcloud-News.git
synced 2024-11-21 22:42:48 +01:00
a0ab07fdb9
Previously when pressing the `O` key on article list, the handler for that keypress first simulated a click on that event in order to mark it as read, and only then opened the website that item links to in another tab. When having a lot of items on screen this caused a huge delay between pressing `O` and opening the linked article in a new tab. The delay was sometimes 5, even 10 whole seconds. This simple fix makes it so the article opens first, and then the click simulation happens afterwards. Signed-off-by: Kuba Orlik <kontakt@kuba-orlik.name> Signed-off-by: Benjamin Brahmer <info@b-brahmer.de>
461 lines
15 KiB
JavaScript
461 lines
15 KiB
JavaScript
/**
|
|
* Nextcloud - News
|
|
*
|
|
* This file is licensed under the Affero General Public License version 3 or
|
|
* later. See the COPYING file.
|
|
*
|
|
* @author Bernhard Posselt <dev@bernhard-posselt.com>
|
|
* @copyright Bernhard Posselt 2014
|
|
*/
|
|
|
|
/**
|
|
* Code in here acts only as a click shortcut mechanism. That's why its not
|
|
* being put into a directive since it has to be tested with protractor
|
|
* anyways and theres no benefit from wiring it into the angular app
|
|
*/
|
|
(function (window, document, $) {
|
|
'use strict';
|
|
|
|
var scrollElement = function() {
|
|
// This should be in sync with the same function in js/directive/NewsScroll.js
|
|
if (window.NEWS_NC_MAJOR_VERSION >= 25) {
|
|
return $('#app-content');
|
|
}
|
|
return $(window);
|
|
};
|
|
|
|
var noInputFocused = function (element) {
|
|
return !(
|
|
element.is('input') ||
|
|
element.is('select') ||
|
|
element.is('textarea') ||
|
|
element.is('checkbox')
|
|
);
|
|
};
|
|
|
|
var noModifierKey = function (event) {
|
|
return !(
|
|
event.shiftKey ||
|
|
event.altKey ||
|
|
event.ctrlKey ||
|
|
event.metaKey
|
|
);
|
|
};
|
|
|
|
var markAllRead = function (navigationArea) {
|
|
var selector = '.active > .app-navigation-entry-menu .mark-read button';
|
|
var button = navigationArea.find(selector);
|
|
if (button.length > 0) {
|
|
button.trigger('click');
|
|
}
|
|
};
|
|
|
|
var isInScrollView = function (elem, scrollArea) {
|
|
// offset().top adds the navigation bar too so we have to subract it
|
|
var elemTop = elem.offset().top - scrollArea.offset().top;
|
|
var elemBottom = elemTop + elem.height();
|
|
|
|
var areaBottom = scrollArea.height();
|
|
|
|
return elemTop >= 0 && elemBottom < areaBottom;
|
|
};
|
|
|
|
var scrollToNavigationElement = function (elem, scrollArea, toTop) {
|
|
if (elem.length === 0 || (!toTop && isInScrollView(elem, scrollArea))) {
|
|
return;
|
|
}
|
|
scrollArea.scrollTop(
|
|
elem.offset().top - scrollArea.offset().top + scrollArea.scrollTop()
|
|
);
|
|
};
|
|
|
|
var scrollToActiveNavigationEntry = function (navigationArea) {
|
|
var element = navigationArea.find('.active');
|
|
scrollToNavigationElement(element, navigationArea.children('ul'), true);
|
|
};
|
|
|
|
var reloadFeed = function (navigationArea) {
|
|
navigationArea.find('.active > a:visible').trigger('click');
|
|
};
|
|
|
|
var activateNavigationEntry = function (element, navigationArea) {
|
|
element.children('a:visible').trigger('click');
|
|
scrollToNavigationElement(element, navigationArea.children('ul'));
|
|
};
|
|
|
|
var nextFeed = function (navigationArea) {
|
|
var current = navigationArea.find('.active');
|
|
var elements = navigationArea.find('.explore-feed,' +
|
|
'.subscriptions-feed:visible,' +
|
|
'.starred-feed:visible,' +
|
|
'.feed:visible');
|
|
|
|
if (current.hasClass('folder')) {
|
|
while (current.length > 0) {
|
|
var subfeeds = current.find('.feed:visible');
|
|
if (subfeeds.length > 0) {
|
|
activateNavigationEntry($(subfeeds[0]), navigationArea);
|
|
return;
|
|
}
|
|
current = current.next('.folder');
|
|
}
|
|
|
|
// no subfeed found
|
|
return;
|
|
}
|
|
|
|
// FIXME: O(n) runtime. If someone creates a nice and not fugly solution
|
|
// please create a PR
|
|
for (var i = 0; i < elements.length - 1; i += 1) {
|
|
var element = elements[i];
|
|
|
|
if (element === current[0]) {
|
|
var next = elements[i + 1];
|
|
activateNavigationEntry($(next), navigationArea);
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
var getParentFolder = function (current) {
|
|
return current.parent().parent('.folder');
|
|
};
|
|
|
|
var selectFirstOrLastFolder = function (navigationArea, isLast) {
|
|
var folders = navigationArea.find('.folder:visible');
|
|
|
|
var index;
|
|
if (isLast) {
|
|
index = folders.length - 1;
|
|
} else {
|
|
index = 0;
|
|
}
|
|
|
|
if (folders.length > 0) {
|
|
activateNavigationEntry($(folders[index]), navigationArea);
|
|
}
|
|
};
|
|
|
|
var previousFolder = function (navigationArea) {
|
|
var current = navigationArea.find('.active');
|
|
|
|
// cases: folder active, subfeed active, feed active, none active
|
|
if (current.hasClass('folder')) {
|
|
activateNavigationEntry(current.prevAll('.folder:visible').first(),
|
|
navigationArea);
|
|
} else if (current.hasClass('feed')) {
|
|
var parentFolder = getParentFolder(current);
|
|
if (parentFolder.length > 0) {
|
|
// first go to previous folder should select the parent folder
|
|
activateNavigationEntry(parentFolder, navigationArea);
|
|
} else {
|
|
selectFirstOrLastFolder(navigationArea, true);
|
|
}
|
|
} else {
|
|
selectFirstOrLastFolder(navigationArea, true);
|
|
}
|
|
};
|
|
|
|
var nextFolder = function (navigationArea) {
|
|
var current = navigationArea.find('.active');
|
|
|
|
// cases: folder active, subfeed active, feed active, none active
|
|
if (current.hasClass('folder')) {
|
|
activateNavigationEntry(current.nextAll('.folder:visible').first(),
|
|
navigationArea);
|
|
} else if (current.hasClass('feed')) {
|
|
var parentFolder = getParentFolder(current);
|
|
if (parentFolder.length > 0) {
|
|
activateNavigationEntry(
|
|
parentFolder.nextAll('.folder:visible').first(),
|
|
navigationArea
|
|
);
|
|
} else {
|
|
selectFirstOrLastFolder(navigationArea);
|
|
}
|
|
} else {
|
|
selectFirstOrLastFolder(navigationArea);
|
|
}
|
|
};
|
|
|
|
var previousFeed = function (navigationArea) {
|
|
var current = navigationArea.find('.active');
|
|
var elements = navigationArea.find('.explore-feed,' +
|
|
'.subscriptions-feed:visible,' +
|
|
'.starred-feed:visible,' +
|
|
'.feed:visible');
|
|
|
|
// special case: folder selected
|
|
if (current.hasClass('folder')) {
|
|
var previousFolder = current.prev('.folder');
|
|
|
|
while (previousFolder.length > 0) {
|
|
var subfeeds = previousFolder.find('.feed:visible');
|
|
if (subfeeds.length > 0) {
|
|
activateNavigationEntry($(subfeeds[subfeeds.length - 1]),
|
|
navigationArea);
|
|
return;
|
|
}
|
|
previousFolder = previousFolder.prev('.folder');
|
|
}
|
|
|
|
// no subfeed found try visible feeds
|
|
var feeds = current.siblings('.feed');
|
|
|
|
if (feeds.length > 0) {
|
|
activateNavigationEntry($(feeds[feeds.length - 1]),
|
|
navigationArea);
|
|
return;
|
|
}
|
|
|
|
|
|
// no feed found, go to starred
|
|
var starred = $('.starred-feed:visible');
|
|
if (starred.length > 0) {
|
|
activateNavigationEntry(starred, navigationArea);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// FIXME: O(n) runtime. If someone creates a nice and not fugly solution
|
|
// please create a PR
|
|
for (var i = elements.length - 1; i > 0; i -= 1) {
|
|
var element = elements[i];
|
|
|
|
if (element === current[0]) {
|
|
var previous = elements[i - 1];
|
|
activateNavigationEntry($(previous), navigationArea);
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
var getActiveElement = function () {
|
|
return $('#app-content').find('.item.active:first');
|
|
};
|
|
|
|
var onActiveItem = function (callback) {
|
|
callback(getActiveElement());
|
|
};
|
|
|
|
var toggleUnread = function () {
|
|
onActiveItem(function (item) {
|
|
item.find('.toggle-keep-unread').trigger('click');
|
|
});
|
|
};
|
|
|
|
var toggleStar = function () {
|
|
onActiveItem(function (item) {
|
|
item.find('.star').trigger('click');
|
|
});
|
|
};
|
|
|
|
var expandItem = function () {
|
|
onActiveItem(function (item) {
|
|
item.find('.utils').trigger('click');
|
|
});
|
|
};
|
|
|
|
var openLink = function () {
|
|
onActiveItem(function (item) {
|
|
var url = item.find('.external:visible').attr('href');
|
|
var newWindow = window.open(url, '_blank');
|
|
newWindow.opener = null;
|
|
setTimeout(()=>item.trigger('click'), 0); // mark read
|
|
});
|
|
};
|
|
|
|
var setItemActive = function (element) {
|
|
element.dispatchEvent(new CustomEvent('set-active'));
|
|
};
|
|
|
|
var scrollToItem = function (scrollArea, item, expandItemInCompact) {
|
|
// if you go to the next article in compact view, it should
|
|
// expand the current one
|
|
|
|
if (window.NEWS_NC_MAJOR_VERSION >= 25) {
|
|
scrollArea.scrollTop(scrollArea.scrollTop() + item.offset().top - 50);
|
|
} else {
|
|
scrollArea.scrollTop(
|
|
item.offset().top - 50
|
|
);
|
|
}
|
|
|
|
setItemActive(item[0]);
|
|
|
|
if (expandItemInCompact) {
|
|
if (!item.hasClass('open')) {
|
|
item.find('.utils').trigger('click');
|
|
}
|
|
}
|
|
};
|
|
|
|
var scrollToNextItem = function (scrollArea, expandItemInCompact) {
|
|
var activeElement = getActiveElement();
|
|
// in expand in compact mode, jumping to the next item should open
|
|
// the current one if it's not open yet
|
|
if (expandItemInCompact && !activeElement.hasClass('open')) {
|
|
activeElement.find('.utils').trigger('click');
|
|
} else {
|
|
var nextElement = activeElement.next();
|
|
if (nextElement.length > 0) {
|
|
scrollToItem(scrollArea, nextElement, expandItemInCompact);
|
|
} else if (nextElement.length === 0) {
|
|
activeElement.find('.utils').trigger('click');
|
|
} else {
|
|
// in case this is the last item it should still scroll below
|
|
// the
|
|
scrollArea.scrollTop(scrollArea.prop('scrollHeight'));
|
|
}
|
|
}
|
|
};
|
|
|
|
var scrollToPreviousItem = function (scrollArea,
|
|
expandItemInCompact) {
|
|
var activeElement = getActiveElement();
|
|
var previousElement = activeElement.prev();
|
|
|
|
// if the active element has been scrolled, the previous element
|
|
// should be the active one
|
|
if (activeElement.position().top + 20 <= 0) {
|
|
scrollToItem(scrollArea, activeElement, expandItemInCompact);
|
|
} else if (previousElement.length > 0) {
|
|
scrollToItem(scrollArea, previousElement, expandItemInCompact);
|
|
} else {
|
|
scrollArea.scrollTop(0);
|
|
}
|
|
};
|
|
|
|
// mark current item as active when scrolling
|
|
$(document).ready(function () {
|
|
var detectAndSetActiveItem = function () {
|
|
var items = $('#app-content').find('.item');
|
|
items.each(function (index, item) {
|
|
var $item = $(item);
|
|
var bottom = $item.position().top + $item.outerHeight(true);
|
|
var scrollBottom = scrollElement().scrollTop();
|
|
if (bottom - 20 >= scrollBottom) {
|
|
setItemActive(item);
|
|
return false;
|
|
}
|
|
});
|
|
};
|
|
scrollElement().scroll(_.debounce(detectAndSetActiveItem, 250));
|
|
});
|
|
|
|
$(document).keyup(function (event) {
|
|
var keyCode = event.keyCode;
|
|
var scrollArea = scrollElement();
|
|
var navigationArea = $('#app-navigation');
|
|
var isCompactView = $('#articles.compact').length > 0;
|
|
var isExpandItem = $('#articles')
|
|
.attr('news-compact-expand') === 'true';
|
|
var expandItemInCompact = isCompactView && isExpandItem;
|
|
|
|
if (noInputFocused($(':focus')) && noModifierKey(event)) {
|
|
// j, n, right arrow
|
|
if ([74, 78, 39].indexOf(keyCode) >= 0) {
|
|
|
|
event.preventDefault();
|
|
scrollToNextItem(scrollArea, expandItemInCompact);
|
|
|
|
// k, p, left arrow
|
|
} else if ([75, 80, 37].indexOf(keyCode) >= 0) {
|
|
|
|
event.preventDefault();
|
|
scrollToPreviousItem(scrollArea,
|
|
expandItemInCompact);
|
|
|
|
// u
|
|
} else if ([85].indexOf(keyCode) >= 0) {
|
|
|
|
event.preventDefault();
|
|
toggleUnread(scrollArea);
|
|
|
|
// e
|
|
} else if ([69].indexOf(keyCode) >= 0) {
|
|
|
|
event.preventDefault();
|
|
expandItem(scrollArea);
|
|
|
|
// s, i, l
|
|
} else if ([73, 83, 76].indexOf(keyCode) >= 0) {
|
|
|
|
event.preventDefault();
|
|
toggleStar(scrollArea);
|
|
|
|
// h
|
|
} else if ([72].indexOf(keyCode) >= 0) {
|
|
|
|
event.preventDefault();
|
|
toggleStar(scrollArea);
|
|
scrollToNextItem(scrollArea);
|
|
|
|
// o
|
|
} else if ([79].indexOf(keyCode) >= 0) {
|
|
|
|
event.preventDefault();
|
|
openLink(scrollArea);
|
|
|
|
// r
|
|
} else if ([82].indexOf(keyCode) >= 0) {
|
|
|
|
event.preventDefault();
|
|
reloadFeed(navigationArea);
|
|
|
|
// f
|
|
} else if ([70].indexOf(keyCode) >= 0) {
|
|
|
|
event.preventDefault();
|
|
nextFeed(navigationArea);
|
|
|
|
// d
|
|
} else if ([68].indexOf(keyCode) >= 0) {
|
|
|
|
event.preventDefault();
|
|
previousFeed(navigationArea);
|
|
|
|
// c
|
|
} else if ([67].indexOf(keyCode) >= 0) {
|
|
|
|
event.preventDefault();
|
|
previousFolder(navigationArea);
|
|
|
|
// a
|
|
} else if ([65].indexOf(keyCode) >= 0) {
|
|
|
|
event.preventDefault();
|
|
scrollToActiveNavigationEntry(navigationArea);
|
|
|
|
// v
|
|
} else if ([86].indexOf(keyCode) >= 0) {
|
|
|
|
event.preventDefault();
|
|
nextFolder(navigationArea);
|
|
|
|
// q
|
|
} else if ([81].indexOf(keyCode) >= 0) {
|
|
|
|
event.preventDefault();
|
|
$('#searchbox').focus();
|
|
|
|
// page up
|
|
}
|
|
|
|
// everything with shift, just the shift
|
|
} else if (noInputFocused($(':focus')) && event.shiftKey &&
|
|
!event.ctrlKey && !event.altKey && !event.metaKey) {
|
|
|
|
// shift + a
|
|
if ([65].indexOf(keyCode) >= 0) {
|
|
|
|
event.preventDefault();
|
|
markAllRead(navigationArea);
|
|
|
|
}
|
|
}
|
|
});
|
|
|
|
}(window, document, $));
|