Big update
This commit is contained in:
parent
b1fdc0767a
commit
f035f32967
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +0,0 @@
|
||||
src/data/
|
@ -1,4 +1,4 @@
|
||||
Miniflux - Minimalist Feed Reader
|
||||
Miniflux - Minimalist News Reader
|
||||
=================================
|
||||
|
||||
Miniflux is a minimalist web-based news reader.
|
||||
@ -16,22 +16,22 @@ Features
|
||||
- Protected by a login/password (only one possible user)
|
||||
- Use secure headers (only external images are allowed)
|
||||
- Open external links inside a new tab with a `rel="noreferrer"` attribute
|
||||
- Mobile CSS (responsive design)
|
||||
|
||||
Todo
|
||||
----
|
||||
|
||||
- Remove older items from the database
|
||||
- Mobile CSS
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
- PHP >= 5.3
|
||||
- XML extensions (SimpleXML, DOM...)
|
||||
- Sqlite
|
||||
- PHP XML extensions (SimpleXML, DOM...)
|
||||
- PHP Sqlite extensions
|
||||
|
||||
Dependencies
|
||||
------------
|
||||
Libraries used
|
||||
--------------
|
||||
|
||||
- [PicoFeed](https://github.com/fguillot/picoFeed)
|
||||
- [PicoFarad](https://github.com/fguillot/picoFarad)
|
||||
@ -42,4 +42,33 @@ Dependencies
|
||||
Installation
|
||||
------------
|
||||
|
||||
In progress...
|
||||
1. You must have a web server with PHP installed (version 5.3 minimum) with the Sqlite and XML extensions
|
||||
2. Download the source code and copy the directory miniflux where you want
|
||||
3. Check if the directory data is writeable (Miniflux store everything inside a Sqlite database)
|
||||
4. With your browser go to <http://yourpersonalserver/miniflux>
|
||||
5. The default login and password is admin/admin
|
||||
6. Start to use the software
|
||||
|
||||
FAQ
|
||||
----
|
||||
|
||||
### How to update your feeds with a cronjob?
|
||||
|
||||
You just need to be inside the directory `miniflux` and run the script `cronjob.php`.
|
||||
|
||||
By example:
|
||||
|
||||
crontab -e
|
||||
|
||||
0 */4 * * * cd /path/to/miniflux && php cronjob.php >/dev/null 2>&1
|
||||
|
||||
|
||||
### How Miniflux update my feeds from the user interface?
|
||||
|
||||
Miniflux use an Ajax request to refresh each subscription.
|
||||
By default, there is only 5 feeds updated in parallel.
|
||||
|
||||
|
||||
### I have 600 subscriptions, how Miniflux handle that?
|
||||
|
||||
Your life is cluttered.
|
||||
|
@ -1,3 +1,4 @@
|
||||
figure,
|
||||
li,
|
||||
ul,
|
||||
table,
|
||||
@ -17,6 +18,8 @@ body {
|
||||
max-width: 750px;
|
||||
color: #333;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
a {
|
||||
@ -49,6 +52,71 @@ h3 {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
border-left: 4px solid #ddd;
|
||||
padding-left: 25px;
|
||||
margin-left: 20px;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
color: #777;
|
||||
line-height: 1.4em;
|
||||
font-family: Georgia, serif;
|
||||
}
|
||||
|
||||
blockquote + p {
|
||||
color: #555;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
figcaption {
|
||||
font-size: 0.8em;
|
||||
text-transform: uppercase;
|
||||
color: #777;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
table caption {
|
||||
font-weight: bold;
|
||||
font-size: 1.0em;
|
||||
text-align: left;
|
||||
padding-bottom: 0.5em;
|
||||
padding-top: 0.5em;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
border: 1px solid #ccc;
|
||||
padding-top: 0.5em;
|
||||
padding-bottom: 0.5em;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
pre {
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 10px;
|
||||
background: #f0f0f0;
|
||||
padding: 0.8em;
|
||||
overflow: auto;
|
||||
color: brown;
|
||||
}
|
||||
|
||||
code {
|
||||
color: brown;
|
||||
}
|
||||
|
||||
caption code,
|
||||
p code {
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
/* forms */
|
||||
form {
|
||||
@ -63,6 +131,10 @@ label {
|
||||
width: 10em;
|
||||
}
|
||||
|
||||
input {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
input[type="email"],
|
||||
input[type="tel"],
|
||||
input[type="password"],
|
||||
@ -160,7 +232,7 @@ textarea.form-error {
|
||||
|
||||
/* buttons */
|
||||
.btn {
|
||||
display: block;
|
||||
display: inline-block;
|
||||
color: #333;
|
||||
border: 1px solid #ccc;
|
||||
background: #efefef;
|
||||
@ -172,6 +244,24 @@ textarea.form-error {
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
a.btn {
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.btn-red {
|
||||
border-color: #b0281a;;
|
||||
background: #d14836;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
a.btn-red:hover,
|
||||
.btn-red:hover,
|
||||
.btn-red:focus {
|
||||
color: #fff;
|
||||
background: #c53727;
|
||||
}
|
||||
|
||||
.btn-blue {
|
||||
border-color: #3079ed;
|
||||
background: #4d90fe;
|
||||
@ -237,6 +327,7 @@ nav .active a {
|
||||
}
|
||||
|
||||
.page-header li {
|
||||
font-size: 90%;
|
||||
display: inline;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
@ -256,10 +347,10 @@ nav .active a {
|
||||
|
||||
.items h2 {
|
||||
font-size: 100%;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
padding-bottom: 2px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.items a {
|
||||
@ -267,7 +358,7 @@ nav .active a {
|
||||
}
|
||||
|
||||
.items a:hover,
|
||||
.items :focus {
|
||||
.items a:focus {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
@ -280,14 +371,29 @@ nav .active a {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.items .preview {
|
||||
color: #555;
|
||||
line-height: 1.5em;
|
||||
font-size: 100%;
|
||||
font-family: Georgia, serif;
|
||||
}
|
||||
|
||||
|
||||
/* item */
|
||||
.item {
|
||||
font-size: 110%;
|
||||
color: #444;
|
||||
color: #333;
|
||||
padding-bottom: 50px;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.item p,
|
||||
.item li {
|
||||
font-family: Georgia, serif;
|
||||
line-height: 1.6em;
|
||||
}
|
||||
|
||||
.item h2,
|
||||
.item h3 {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.item pre,
|
||||
@ -305,19 +411,6 @@ nav .active a {
|
||||
margin-left: 25px;
|
||||
}
|
||||
|
||||
.item li {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.item pre {
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 10px;
|
||||
background: #f0f0f0;
|
||||
padding: 20px;
|
||||
overflow: auto;
|
||||
color: brown;
|
||||
}
|
||||
|
||||
.item img {
|
||||
display: block;
|
||||
margin-top: 15px;
|
||||
@ -325,29 +418,71 @@ nav .active a {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.item code {
|
||||
color: brown;
|
||||
}
|
||||
|
||||
.infos {
|
||||
padding-bottom: 30px;
|
||||
padding-bottom: 20px;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.item h1 {
|
||||
}
|
||||
|
||||
.item h1 a {
|
||||
font-size: 150%;
|
||||
font-size: 2.1em;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
border-left: 4px solid #ddd;
|
||||
padding-left: 25px;
|
||||
margin-left: 20px;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
color: #666;
|
||||
line-height: 22px;
|
||||
.item a:visited {
|
||||
color: purple;
|
||||
}
|
||||
|
||||
|
||||
/* mobile design */
|
||||
@media only screen and (max-width: 480px) {
|
||||
|
||||
body {
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
max-width: 480px;
|
||||
}
|
||||
|
||||
nav .active a {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: block;
|
||||
float: none;
|
||||
}
|
||||
|
||||
header {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
header ul {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
header li {
|
||||
padding: 0;
|
||||
width: 50%;
|
||||
float: right;
|
||||
display: block;
|
||||
line-height: 25px;
|
||||
}
|
||||
|
||||
.page {
|
||||
clear: both;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.item {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.item h1 {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.item .infos {
|
||||
font-size: 0.9em;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 847 B After Width: | Height: | Size: 847 B |
@ -56,7 +56,7 @@
|
||||
|
||||
if (! response.result) {
|
||||
|
||||
window.alert('Unable to refresh this feed: ' + feed_id);
|
||||
//window.alert('Unable to refresh this feed: ' + feed_id);
|
||||
}
|
||||
}
|
||||
catch (e) {}
|
1
miniflux/data/.gitignore
vendored
Normal file
1
miniflux/data/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
!.gitignore
|
@ -95,13 +95,28 @@ Router\get_action('history', function() {
|
||||
});
|
||||
|
||||
|
||||
Router\get_action('confirm-remove', function() {
|
||||
|
||||
$id = Request\int_param('feed_id');
|
||||
|
||||
Response\html(Template\layout('confirm_remove', array(
|
||||
'feed' => Model\get_feed($id),
|
||||
'menu' => 'feeds'
|
||||
)));
|
||||
});
|
||||
|
||||
|
||||
Router\get_action('remove', function() {
|
||||
|
||||
$id = Request\int_param('feed_id');
|
||||
|
||||
if ($id) {
|
||||
if ($id && Model\remove_feed($id)) {
|
||||
|
||||
Model\remove_feed($id);
|
||||
Session\flash('This subscription has been removed successfully');
|
||||
}
|
||||
else {
|
||||
|
||||
Session\flash_error('Unable to remove this subscription');
|
||||
}
|
||||
|
||||
Response\redirect('?action=feeds');
|
||||
@ -160,6 +175,7 @@ Router\get_action('feeds', function() {
|
||||
|
||||
Response\html(Template\layout('feeds', array(
|
||||
'feeds' => Model\get_feeds(),
|
||||
'nothing_to_read' => Request\int_param('nothing_to_read'),
|
||||
'menu' => 'feeds'
|
||||
)));
|
||||
});
|
||||
@ -265,8 +281,15 @@ Router\post_action('config', function() {
|
||||
|
||||
Router\notfound(function() {
|
||||
|
||||
$items = Model\get_unread_items();
|
||||
|
||||
if (empty($items)) {
|
||||
|
||||
Response\redirect('?action=feeds¬hing_to_read=1');
|
||||
}
|
||||
|
||||
Response\html(Template\layout('unread_items', array(
|
||||
'items' => Model\get_unread_items(),
|
||||
'items' => $items,
|
||||
'menu' => 'unread'
|
||||
)));
|
||||
});
|
@ -129,7 +129,7 @@ function get_unread_items()
|
||||
{
|
||||
return \PicoTools\singleton('db')
|
||||
->table('items')
|
||||
->columns('items.id', 'items.title', 'items.updated', 'items.url', 'feeds.site_url')
|
||||
->columns('items.id', 'items.title', 'items.updated', 'items.url', 'feeds.site_url', 'items.content')
|
||||
->join('feeds', 'id', 'feed_id')
|
||||
->eq('status', 'unread')
|
||||
->desc('updated')
|
||||
@ -197,11 +197,6 @@ function update_feeds()
|
||||
|
||||
update_items($feed['id'], $parser->execute()->items);
|
||||
}
|
||||
else {
|
||||
|
||||
print_r($feed);
|
||||
die;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
10
miniflux/templates/confirm_remove.php
Normal file
10
miniflux/templates/confirm_remove.php
Normal file
@ -0,0 +1,10 @@
|
||||
<div class="page-header">
|
||||
<h2>Confirmation</h2>
|
||||
</div>
|
||||
|
||||
<p class="alert alert-info">Do you really want to remove this subscription: "<?= Helper\escape($feed['title']) ?>"?</p>
|
||||
|
||||
<div class="form-actions">
|
||||
<a href="?action=remove&feed_id=<?= $feed['id'] ?>" class="btn btn-red">Yes</a>
|
||||
or <a href="?action=feeds">cancel</a>
|
||||
</div>
|
@ -14,6 +14,10 @@
|
||||
|
||||
<?php else: ?>
|
||||
|
||||
<?php if ($nothing_to_read): ?>
|
||||
<p class="alert">Nothing to read, do you want to <a href="?action=refresh-all" data-action="refresh-all">update your subscriptions?</a></p>
|
||||
<?php endif ?>
|
||||
|
||||
<section class="items">
|
||||
<?php foreach ($feeds as $feed): ?>
|
||||
<article>
|
||||
@ -23,7 +27,8 @@
|
||||
</h2>
|
||||
<p>
|
||||
<?= Helper\get_host_from_url($feed['site_url']) ?> |
|
||||
<a href="?action=remove&feed_id=<?= $feed['id'] ?>">remove</a> |
|
||||
<a href="<?= Helper\escape($feed['feed_url']) ?>">feed link</a> |
|
||||
<a href="?action=confirm-remove&feed_id=<?= $feed['id'] ?>">remove</a> |
|
||||
<a href="?action=refresh-feed&feed_id=<?= $feed['id'] ?>" data-feed-id="<?= $feed['id'] ?>" data-action="refresh-feed">refresh</a>
|
||||
</p>
|
||||
</article>
|
@ -1,6 +1,6 @@
|
||||
<?php if (empty($items)): ?>
|
||||
|
||||
<p class="alert alert-info">No unread items.</p>
|
||||
<p class="alert alert-info">Nothing to read.</p>
|
||||
|
||||
<?php else: ?>
|
||||
|
||||
@ -15,6 +15,9 @@
|
||||
<?php foreach ($items as $item): ?>
|
||||
<article>
|
||||
<h2><a href="?action=read&id=<?= 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">direct link</a>
|
@ -40,7 +40,12 @@ class Filter
|
||||
'br' => array(),
|
||||
'del' => array(),
|
||||
'a' => array('href'),
|
||||
'img' => array('src', 'width', 'height')
|
||||
'img' => array('src'),
|
||||
'figure' => array(),
|
||||
'figcaption' => array(),
|
||||
'cite' => array(),
|
||||
'time' => array('datetime'),
|
||||
'abbr' => array('title')
|
||||
);
|
||||
|
||||
public $strip_tags_content = array(
|
@ -55,6 +55,23 @@ function get_host_from_url($url)
|
||||
}
|
||||
|
||||
|
||||
function summary($value, $min_length = 5, $max_length = 120, $end = '[...]')
|
||||
{
|
||||
$length = strlen($value);
|
||||
|
||||
if ($length > $max_length) {
|
||||
|
||||
return substr($value, 0, strpos($value, ' ', $max_length)).' '.$end;
|
||||
}
|
||||
else if ($length < $min_length) {
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
|
||||
function in_list($id, array $listing)
|
||||
{
|
||||
if (isset($listing[$id])) {
|
Binary file not shown.
Before Width: | Height: | Size: 75 KiB |
Binary file not shown.
Before Width: | Height: | Size: 154 KiB |
Binary file not shown.
Before Width: | Height: | Size: 55 KiB |
Loading…
Reference in New Issue
Block a user