Add keyboard shortcuts

This commit is contained in:
Frederic Guillot 2013-04-02 22:49:14 -04:00
parent 93ce3e170a
commit 94c1beb19e
9 changed files with 361 additions and 22 deletions

View File

@ -234,6 +234,12 @@ textarea.form-error {
border-color: #bce8f1;
}
.alert-normal {
color: #333;
background-color: #f0f0f0;
border-color: #ddd;
}
/* buttons */
.btn {
@ -389,6 +395,11 @@ nav .active a {
font-family: Georgia, serif;
}
.items #current-item {
border: 2px dashed #d14;
padding: 5px;
}
/* item */
.item {

View File

@ -5,18 +5,36 @@
var queue_length = 5;
function switch_status(item_id)
{
var request = new XMLHttpRequest();
request.onreadystatechange = function() {
if (request.readyState === 4) {
var response = JSON.parse(request.responseText);
if (response.status == "read" || response.status == "unread") {
find_next_item();
remove_item(response.item_id);
}
}
}
request.open("POST", "?action=status&id=" + item_id, true);
request.send();
}
function mark_as_read(item_id)
{
var request = new XMLHttpRequest();
request.onload = function() {
var article = document.getElementById("item-" + item_id);
if (article) {
article.style.display = "none";
}
remove_item(item_id);
};
request.open("POST", "?action=read&id=" + item_id, true);
@ -24,6 +42,15 @@
}
function mark_as_unread(item_id)
{
var request = new XMLHttpRequest();
request.open("POST", "?action=unread&id=" + item_id, true);
request.send();
}
function show_refresh_icon(feed_id)
{
var container = document.getElementById("loading-feed-" + feed_id);
@ -137,6 +164,157 @@
}
function remove_item(item_id)
{
var item = document.getElementById("item-" + item_id);
if (item) item.parentNode.removeChild(item);
}
function open_original_item()
{
var link = document.getElementById("original-item");
if (link) {
mark_as_read(link.getAttribute("data-item-id"));
find_next_item();
link.click();
}
}
function open_item()
{
var link = document.getElementById("open-item");
if (link) link.click();
}
function open_next_item()
{
var link = document.getElementById("next-item");
if (link) {
link.click();
}
else if (is_listing()) {
find_next_item();
}
}
function open_previous_item()
{
var link = document.getElementById("previous-item");
if (link) {
link.click();
}
else if (is_listing()) {
find_previous_item();
}
}
function change_item_status()
{
var item = document.getElementById("current-item");
if (item) switch_status(item.getAttribute("data-item-id"));
}
function set_links_item(item_id)
{
var link = document.getElementById("original-item");
if (link) link.id = "original-" + link.getAttribute("data-item-id");
var link = document.getElementById("open-item");
if (link) link.id = "open-" + link.getAttribute("data-item-id");
var link = document.getElementById("original-" + item_id);
if (link) link.id = "original-item";
var link = document.getElementById("open-" + item_id);
if (link) link.id = "open-item";
}
function find_next_item()
{
var items = document.getElementsByTagName("article");
if (! document.getElementById("current-item")) {
items[0].id = "current-item";
set_links_item(items[0].getAttribute("data-item-id"));
}
else {
for (var i = 0, ilen = items.length; i < ilen; i++) {
if (items[i].id == "current-item") {
items[i].id = "item-" + items[i].getAttribute("data-item-id");
if (i + 1 < ilen) {
items[i + 1].id = "current-item";
set_links_item(items[i + 1].getAttribute("data-item-id"));
}
break;
}
}
}
}
function find_previous_item()
{
var items = document.getElementsByTagName("article");
if (! document.getElementById("current-item")) {
items[items.length - 1].id = "current-item";
set_links_item(items[items.length - 1].getAttribute("data-item-id"));
}
else {
for (var i = items.length - 1; i >= 0; i--) {
if (items[i].id == "current-item") {
items[i].id = "item-" + items[i].getAttribute("data-item-id");
if (i - 1 >= 0) {
items[i - 1].id = "current-item";
set_links_item(items[i - 1].getAttribute("data-item-id"));
}
break;
}
}
}
}
function is_listing()
{
if (document.getElementById("listing")) {
return true;
}
return false;
}
document.onclick = function(e) {
var action = e.target.getAttribute("data-action");
@ -161,4 +339,25 @@
}
};
document.onkeypress = function(e) {
switch (e.keyCode || e.which) {
case 112:
open_previous_item();
break;
case 110:
open_next_item();
break;
case 118:
open_original_item();
break;
case 111:
open_item();
break;
case 109:
change_item_status();
break;
}
};
})();

View File

@ -9,6 +9,11 @@ require 'schema.php';
require 'model.php';
const DB_VERSION = 1;
const APP_VERSION = 'master';
// For future use...
function get_db_filename()
{
return 'data/db.sqlite';
@ -22,7 +27,7 @@ PicoTools\container('db', function() {
'filename' => get_db_filename()
));
if ($db->schema()->check(1)) {
if ($db->schema()->check(DB_VERSION)) {
return $db;
}

View File

@ -108,6 +108,27 @@ Router\post_action('read', function() {
});
Router\post_action('unread', function() {
$id = Request\param('id');
Model\set_item_unread($id);
Response\json(array('Ok'));
});
Router\post_action('status', function() {
$id = Request\param('id');
Response\json(array(
'item_id' => urlencode($id),
'status' => Model\switch_item_status($id)
));
});
Router\get_action('history', function() {
Response\html(Template\layout('read_items', array(
@ -171,9 +192,9 @@ Router\get_action('ajax-refresh-feed', function() {
});
Router\get_action('flush-unread', function() {
Router\get_action('mark-as-read', function() {
Model\flush_unread();
Model\mark_as_read();
Response\redirect('?action=unread');
});

View File

@ -191,6 +191,55 @@ function set_item_read($id)
}
function set_item_unread($id)
{
\PicoTools\singleton('db')
->table('items')
->eq('id', $id)
->save(array('status' => 'unread'));
}
function switch_item_status($id)
{
$item = \PicoTools\singleton('db')
->table('items')
->columns('status')
->eq('id', $id)
->findOne();
if ($item['status'] == 'unread') {
\PicoTools\singleton('db')
->table('items')
->eq('id', $id)
->save(array('status' => 'read'));
return 'read';
}
else {
\PicoTools\singleton('db')
->table('items')
->eq('id', $id)
->save(array('status' => 'unread'));
return 'unread';
}
return '';
}
function mark_as_read()
{
\PicoTools\singleton('db')
->table('items')
->eq('status', 'unread')
->save(array('status' => 'read'));
}
function flush_unread()
{
\PicoTools\singleton('db')

View File

@ -30,3 +30,25 @@
<li><a href="?action=download-db">Download the entire database</a> (Gzip compressed Sqlite file).</li>
</ul>
</section>
<div class="page-section">
<h2>Help</h2>
</div>
<section>
<div class="alert">
<h3>Keyboard shortcuts</h3>
<ul>
<li>Previous item = <strong>p</strong></li>
<li>Next item = <strong>n</strong></li>
<li>Mark as read or unread = <strong>m</strong></li>
<li>Open original link = <strong>v</strong></li>
<li>Open item = <strong>o</strong></li>
</ul>
</div>
<div class="alert alert-normal">
<h3>About</h3>
<ul>
<li>Miniflux version: <strong><?= APP_VERSION ?></strong></li>
</ul>
</div>
</section>

View File

@ -4,9 +4,9 @@
<?php else: ?>
<article class="item">
<article class="item" id="current-item" data-item-id="<?= urlencode($item['id']) ?>">
<h1>
<a href="<?= $item['url'] ?>" rel="noreferrer" target="_blank"><?= Helper\escape($item['title']) ?></a>
<a href="<?= $item['url'] ?>" rel="noreferrer" target="_blank" id="original-item"><?= Helper\escape($item['title']) ?></a>
</h1>
<p class="infos">
@ -20,7 +20,7 @@
<nav>
<span class="nav-left">
<?php if ($item_nav['previous']): ?>
<a href="?action=read&amp;id=<?= urlencode($item_nav['previous']['id']) ?>">« Previous</a>
<a href="?action=read&amp;id=<?= urlencode($item_nav['previous']['id']) ?>" id="previous-item">« Previous</a>
<?php else: ?>
« Previous
<?php endif ?>
@ -40,7 +40,7 @@
<span class="nav-right">
<?php if ($item_nav['next']): ?>
<a href="?action=read&amp;id=<?= urlencode($item_nav['next']['id']) ?>">Next »</a>
<a href="?action=read&amp;id=<?= urlencode($item_nav['next']['id']) ?>" id="next-item">Next »</a>
<?php else: ?>
Next »
<?php endif ?>

View File

@ -11,14 +11,30 @@
</ul>
</div>
<section class="items">
<section class="items" id="listing">
<?php foreach ($items as $item): ?>
<article>
<h2><a href="?action=show&amp;id=<?= urlencode($item['id']) ?>"><?= Helper\escape($item['title']) ?></a></h2>
<article id="item-<?= urlencode($item['id']) ?>" data-item-id="<?= urlencode($item['id']) ?>">
<h2>
<a
href="?action=show&amp;id=<?= urlencode($item['id']) ?>"
id="open-<?= urlencode($item['id']) ?>"
>
<?= Helper\escape($item['title']) ?>
</a>
</h2>
<p>
<?= Helper\get_host_from_url($item['url']) ?> |
<?= date('l, j F Y H:i', $item['updated']) ?> |
<a href="<?= $item['url'] ?>" rel="noreferrer" target="_blank">direct link</a>
<a
href="<?= $item['url'] ?>"
id="original-<?= urlencode($item['id']) ?>"
rel="noreferrer"
target="_blank"
data-item-id="<?= urlencode($item['id']) ?>"
data-action="mark-read"
>
direct link
</a>
</p>
</article>
<?php endforeach ?>

View File

@ -7,20 +7,36 @@
<div class="page-header">
<h2>Unread items</h2>
<ul>
<li><a href="?action=flush-unread">flush</a></li>
<li><a href="?action=mark-as-read">mark all as read</a></li>
</ul>
</div>
<section class="items">
<section class="items" id="listing">
<?php foreach ($items as $item): ?>
<article id="item-<?= urlencode($item['id']) ?>">
<h2><a href="?action=read&amp;id=<?= urlencode($item['id']) ?>"><?= Helper\escape($item['title']) ?></a></h2>
<article id="item-<?= urlencode($item['id']) ?>" data-item-id="<?= urlencode($item['id']) ?>">
<h2>
<a
href="?action=read&amp;id=<?= urlencode($item['id']) ?>"
id="open-<?= urlencode($item['id']) ?>"
>
<?= Helper\escape($item['title']) ?>
</a>
</h2>
<p class="preview">
<?= Helper\escape(Helper\summary(strip_tags($item['content']), 50, 300)) ?>
</p>
<p>
<?= Helper\get_host_from_url($item['url']) ?> |
<a href="<?= $item['url'] ?>" rel="noreferrer" target="_blank" data-item-id="<?= urlencode($item['id']) ?>" data-action="mark-read">direct link</a>
<a
href="<?= $item['url'] ?>"
id="original-<?= urlencode($item['id']) ?>"
rel="noreferrer"
target="_blank"
data-item-id="<?= urlencode($item['id']) ?>"
data-action="mark-read"
>
direct link
</a>
</p>
</article>
<?php endforeach ?>