Commit dae9e932 authored by yPhil's avatar yPhil
Browse files

1.2.8

parent ba22b22e
Pipeline #408968 passed with stage
in 52 seconds
......@@ -2,28 +2,41 @@
## 1.2.8
- New option / dialog to merge the imported / open feeds with existing ones #e38691d5
- New function / dialog to merge the imported / open feeds with existing ones #e38691d5
- Server ports now set [in the config file](https://framagit.org/yphil/petrolette/-/blob/master/package.json) #c46149d8
- Separate default feeds file
- It's a pretty big deal : instead of being included and loaded at **each visit of the frigging page**, the ever-growing (nearly 100K as of tonight) default list of feeds is now requested on-demand via a `GET` on the server, only one unique time.
- General UI makeover & optimization (dialogs, wording, misc options, speed)
### Bugfixes
- Better Item layout (Media/comment icons) and wrapping #c8158af2
- Better dialog defaults (WIP #9e3c8c2d)
- Autoselect in the text inputs #c46149d8
- In "photo" mode, vertical images wrapped around the text, fixed #2027005b
- Deselected selected feeds on delete / edit / reload
- Before I can fin the time to code the "bulk" logic to delete / edit / reload all selected feeds in batch, we unselect on drop to ensure UX consistency
- Better item layout (Media/comment icons) and wrapping #c8158af2
- In "photo" mode, vertical images wrapped around the text, fixed #2027005b
- BIG dialog makeover : Extended defaults, shorter templates, removed a lot of boilerplate code, etc. (WIP #9e3c8c2d)
- Some dialogs were not fully translated #e38691d5
- New colors
- Feed type buttons: Removed the "grow / last" class / hack #c46149d8
- Autoselect in all the text inputs #c46149d8
- Feed name || URL in tooltip #b02b049c
- UTC Timestamps #a5ccb4ae
- Console size adjusted #c46149d8
- Console size adjusted for mobile #c46149d8
- Fixed a long, outstanding & known bug where the "new tab" button was not appended after an import, because of the weird gymnastics we have to do to make sure said button is always the last one, think about that when reviewing the (quite convoluted) code #b02b049c
- Some dialogs were not fully translated #e38691d5
- Feed type buttons: Removed the "grow / last" class / hack #c46149d8
- Lib update: NPM 7.10.0
- Removed CORS on the server
- I'm not sure we even need it at all to renew certificates
- The Pétrolette logo is no longer a `a href="#"` but a plain DIV to avoid location re-write onclick (A click on it puts focus on the 1st tab, very handy when you read Pétrolette from the couch with a lousy touchpad)
- Even **More** CSS streamlining
- Big translation review
- Removed a lot of unused phrases in the i18 file (faster *everyday* loading time here too)
- Changed dialog wording policy : Laconic and modular in the titles, explicit and literal in the content text
- [Valid HTML](https://validator.w3.org/nu/?doc=https%3A%2F%2Fpetrolette.space%2F)
## 1.2.7
- The feed's favicon file name / path is now saved along, so as to avoid re-requesting it, then re-computing the (hash) name, then re-rend it back from the server, so everybody wins #1e5f4c20
- The Logging strategy has been slightly adjusted: We now log the access to /feed and no long to /favicon #1e5f4c20
- The Logging strategy has been slightly adjusted: We now log the access to /feed and not to /favicon #1e5f4c20
- External (ie froml the feeds) asset (typically imgs but not only) calls are now forcefully https #d4782908
- Re-enabled server compression #941dac8a
......
# Dev notes
> #### Preamble
> - Please read [the Pétrolette licence file](LICENCE).
> - Please read [the Pétrolette licence file](./LICENCE).
> - The Pétrolette development team have *no* Code Of Conduct, just act responsibly, as in every aspect of life.
> - If you use Pétrolette, both on your own server or on the test instance, please consider [a donation](https://liberapay.com/yPhil/) for its development.
## Conventions
## Conventions & style guide
- Pétrolette functions / methods, JQuery objects / vars and HTML/CSS elements names are in `camelCase`.
JQuery is aliased to `$`, and *all* JQuery objects are prefixed with `$`.
- Single quotes in JS code, double quotes in HTML code.
- Pétrolette functions / methods, JQuery objects / vars are in `camelCase` ;
- HTML / CSS elements names are in `kebab-case` ;
- JQuery is aliased to `$`, and *all* JQuery vars & constants are sigil-prefixed with `$` ;
- Single quotes in JS, double quotes in HTML ;
- Indents: 2 *spaces*.
## Installation notes & caveats
......@@ -17,20 +19,20 @@ The two libs [Feedrat](https://framagit.org/yphil/feedrat) (to discover a RSS/At
## Under the hood
On the front side, Pétrolette is a web app that serves HTML using plain Javascript ES8 and JQuery-UI for the user interface.
On the front side, Pétrolette is a web app that serves HTML using plain Javascript ES8 and JQueryUI for the user interface.
Pétrolette is its own server, a standard [express](https://github.com/expressjs/express) app ; it is "process-managed" by [pm2](https://pm2.keymetrics.io) ; Here is the basic operational rundown:
Pétrolette is its own server, a standard [express](https://github.com/expressjs/express) app, "process-managed" by [pm2](https://pm2.keymetrics.io) ; Here is the basic operational sequence :
- `npm start` launches `pm2` as per [package.json](package.json)
- `pm2` launches [www](bin/www) as per [pm2.config.json](pm2.config.json)
- [www](bin/www) launches [app](app.js)
- [app](app.js) uses [index.js](routes/index.js) to define 3 routes: `/` for the actual Pétrolette page, `/discover` for the RSS searching (see Feedrat) and `/favicon` for the site icon searching (see Favrat).
- The client send the RSS request to the server
- The server returns the RSS content to the client
- `pm2` launches [http/server.js](http/server.js) as per [pm2.config.json](pm2.config.json)
- [http/server.js](http/server.js) launches [petrolette.js](petrolette.js)
- [petrolette.js](petrolette.js) uses [routes/router.js](routes/router.js) to define 4 routes: `/` for the actual Pétrolette page, `/discover` for the RSS searching (see Feedrat), `/favicon` for the site icon searching (see Favrat) and `static` for serving the static (CSS, icons, fonts, etc.) files.
- The client send the (RSS / favicon / discover / static) request to the server
- The server returns the content to the client
At the first startup, Pétrolette generates its page using a default tabs and feeds list, then copies this structured list the the client's local storage persistent cache. The user can (should) also use a third party cloud storage to write / read his / her tabs and feeds, in order to have the same contents on all machines : Desktop, laptop, phone, etc.
Why is there a server in the first place, why can't the client do all the work ? Because of [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS), that's actually the only reason, if you exclude my desire to learn server-side JS :)
Why is there a server in the first place, **why can't the client do all the RSS requests?** Because of [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS), that's actually the *only* reason, if you exclude my desire to learn server-side JS :)
pm2 automatically restarts both Pétrolette when its files are edited (excluding cache and module/lib directories, of course, see [pm2.config.json](pm2.config.json)) and itself if the host machine restarts.
......
# Pétrolette
[![pipeline status](https://framagit.org/yphil/petrolette/badges/master/pipeline.svg)](https://framagit.org/yphil/petrolette/-/pipelines)
[![website](https://img.shields.io/website?down_message=down&up_color=brightgreen&up_message=up&url=https%3A%2F%2Fpetrolette.space)](http://petrolette.space)
[![website](https://img.shields.io/website?down_message=down&up_color=brightgreen&up_message=up&url=https%3A%2F%2Fpetrolette.space)](https://petrolette.space)
[![Liberapay](https://img.shields.io/liberapay/receives/yPhil)](https://liberapay.com/yPhil/donate)
[![Liberapay](https://img.shields.io/liberapay/goal/yPhil)](https://liberapay.com/yPhil/donate)
......@@ -9,7 +9,7 @@
<!-- [![coverage](https://framagit.org/yphil/petrolette/badges/master/coverage.svg)](https://framagit.org/yphil/petrolette/-/pipelines) -->
## Good morning ☕
## Good morning! 📰
[Pétrolette](http://petrolette.space) is a news reading home page, [free](https://framagit.org/yphil/petrolette/-/blob/master/LICENSE). It is immediately usable **without registration** with the same URL on the desktop or a mobile device.
......@@ -21,6 +21,7 @@ News feeds are organized into tabs, which can contain an infinite number of colu
## Features
- No ads, no trackers
- Displays all modern feeds (Atom, Media-RSS, etc.)
- Search within the feeds
- Discovery of the feed in a page / website
......
......@@ -38,23 +38,12 @@ app.set('views', path.join(__dirname, 'views'));
app.engine('html', require('ejs').renderFile);
app.set('view engine', 'html');
app.disable('x-powered-by');
app.use(compression());
// app.use(helmet.originAgentCluster());
// app.use(helmet.noSniff());
// app.use(helmet.frameguard());
// app.use(helmet.frameguard());
app.use(
helmet({
contentSecurityPolicy: false,
})
);
app.use(helmet({contentSecurityPolicy: false}));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.urlencoded({extended: false}));
app.use('/favicons', express.static(path.join(__dirname, pjson.FAVICONS_CACHE_DIR)));
app.use('/static', express.static(path.join(__dirname, 'public')));
......
@font-face {
font-family: 'fontello';
src: url('../font/fontello.eot?31205858');
src: url('../font/fontello.eot?31205858#iefix') format('embedded-opentype'),
url('../font/fontello.woff2?31205858') format('woff2'),
url('../font/fontello.woff?31205858') format('woff'),
url('../font/fontello.ttf?31205858') format('truetype'),
url('../font/fontello.svg?31205858#fontello') format('svg');
src: url('../font/fontello.eot?42225438');
src: url('../font/fontello.eot?42225438#iefix') format('embedded-opentype'),
url('../font/fontello.woff2?42225438') format('woff2'),
url('../font/fontello.woff?42225438') format('woff'),
url('../font/fontello.ttf?42225438') format('truetype'),
url('../font/fontello.svg?42225438#fontello') format('svg');
font-weight: normal;
font-style: normal;
}
......@@ -15,7 +15,7 @@
@media screen and (-webkit-min-device-pixel-ratio:0) {
@font-face {
font-family: 'fontello';
src: url('../font/fontello.svg?31205858#fontello') format('svg');
src: url('../font/fontello.svg?42225438#fontello') format('svg');
}
}
*/
......@@ -56,6 +56,7 @@
}
.icon-down-dir:before { content: '\e800'; } /* '' */
.icon-left:before { content: '\e801'; } /* '' */
.icon-down-circle:before { content: '\e802'; } /* '' */
.icon-upload:before { content: '\e803'; } /* '' */
.icon-up:before { content: '\e804'; } /* '' */
......
......@@ -21,7 +21,7 @@
--color-ui-qui: #808080;
--color-ui-sen: #F26522;
--color-ui-sep: #CCC;
--color-ui-oct: #2B2B2B;
--color-ui-oct: #394655;
--color-ui-non: #007FFF;
--color-ui-ten: #003EFF;
--color-ui-title-one: #FF4136;
......@@ -60,6 +60,7 @@
--title-margin-left: 0.2em;
--columns: auto 3;
--logo-display: inline;
--wrap: nowrap;
}
......@@ -84,6 +85,7 @@
--title-margin-left: 0;
--columns: auto;
--logo-display: none;
--wrap: wrap;
}
}
......@@ -118,17 +120,26 @@ a:active {
color: var(--color-fg2);
}
a.ptlInterface:link {
a.ptl-link:link {
text-decoration: underline;
}
a.ptlInterface:visited {
a.ptl-link:visited {
color: var(--color-ui-non) !important;
}
/* color: var(--color-ui-non); */
a.ptlInterface:hover {
color: var(--color-ui-sec);
a.ptl-link:hover {
color: var(--color-ui-sec) !important;
}
.dangerous {
color: var(--color-ui-danger) !important;
}
.logo-title:hover,
.ptl-link a:hover,
.introjs-tooltiptext a:hover {
color: var(--color-ui-sec) !important;
}
.feed-header .feed-controls div {
......@@ -219,19 +230,15 @@ a.ptlInterface:hover {
div#logo-title {
display: flex;
}
.icon-petrolette {
color: var(--color-ui-sec);
cursor: pointer;
}
.icon-w3c {
color: var(--color-ui-success);
}
.logoType i.icon-petrolette {
i.icon-petrolette {
display: block;
cursor: pointer;
font-size: 24pt;
margin: 6px 12px 6px 6px;
color: var(--color-ui-sec);
......@@ -246,10 +253,6 @@ i.icon-petrolette::before {
font-style: italic !important;
}
i.icon-petrolette:hover {
font-style: normal !important;
}
div#logo-title > div {
flex: 1 0;
}
......@@ -261,14 +264,12 @@ div#logo-title > div {
font-size: 36pt;
}
.ptlInterface a,
.introjs-tooltiptext a {
color: var(--color-ui-sec) !important;
i.icon-petrolette.item-icon {
font-size: 1em;
}
.ptlInterface a:hover,
.introjs-tooltiptext a:hover {
color: var(--color-ui-sen) !important;
.item-icon:hover {
color: var(--color-fg1);
}
.ui-tab.ui-state-active a,
......@@ -418,7 +419,7 @@ h3 {
}
h1 {
color: var(--color-ui-title-one);
color: var(--color-ui-sec);
margin-top: 0;
}
......@@ -430,7 +431,6 @@ h1 {
.ui-dialog p {
margin: 0 0 0.4em 0.4em;
color: var(--color-ui-pri);
}
kbd.key {
......@@ -446,10 +446,6 @@ ul {
list-style-type: none;
}
ul.dialogTextList {
list-style-type: lower-greek;
}
.ui-tab,
.ui-tabs,
ul,
......@@ -457,25 +453,25 @@ li {
padding: 0;
}
#no-script ul {
list-style-type: disc;
/* padding: .2em; */
#question-dialog > .flex-box {
flex-wrap: var(--wrap);
}
#no-script h1 {
#dialog-blurb {
font-style: italic;
color: var(--color-ui-danger);
}
ul.help li {
margin: 1em 0 0 1em;
#dialog-text {
flex-grow: 4; /* default 0 */
}
#no-script ul > li {
margin: 0.3em 0;
legend,
#dialog-text > p {
color: var(--color-ui-pri);
}
/* padding: .2em; */
ul.help li {
margin: 1em 0 0 1em;
}
.ui-tab#new-group {
......@@ -547,10 +543,6 @@ i.item-icon:hover {
color: var(--color-danger);
}
.dangerous:hover {
color: var(--color-ui-danger);
}
.feed-header > .feed-title > a:link,
.feed-header > .feed-title > a:visited,
.feed-header > .feed-title > a:focus,
......@@ -699,15 +691,6 @@ nav#side-menu,
border-color: var(--color-ui-qua);
}
/* nav#side-menu .ui-button, */
/* #sync-box { */
/* margin-bottom: 0.3em; */
/* } */
/* .left { */
/* margin-right: 0.3em; */
/* } */
.results {
outline: 1px solid var(--color-fg7) !important;
}
......@@ -1062,17 +1045,15 @@ img.warning {
width: 10em;
}
.huge {
i.huge {
font-size: 8em;
}
div.dialogContent {
display: flex;
flex-direction: var(--dialog-flex-direction);
i.huge:not(.icon-petrolette) {
color: var(--color-ui-oct);
}
div.dialogContent > div.dialogImage,
div.questionDialog > div.icon {
div#icon {
text-align: center;
flex: 0 0 auto;
min-width: 200px;
......@@ -1386,8 +1367,8 @@ div.introjs-helperLayer {
/* IntroJS (help tooltips) */
.introjs-tooltip {
background-color: var(--color-ui-qua) !important;
border: 1px solid var(--color-ui-oct);
background-color: var(--color-ui-oct) !important;
border: 1px solid var(--color-ui-qua);
color: var(--color-ui-pri) !important;
width: 360px !important;
max-width: 360px !important;
......
......@@ -38,6 +38,12 @@
"code": 59396,
"src": "typicons"
},
{
"uid": "c0a07aca383ff2a164a4d691fa46e35c",
"css": "left",
"code": 59393,
"src": "elusive"
},
{
"uid": "616b755d0f4c7d2dc7a1dcc2e5427039",
"css": "down",
......
No preview for this file type
......@@ -8,6 +8,8 @@
<missing-glyph horiz-adv-x="1000" />
<glyph glyph-name="down-dir" unicode="&#xe800;" d="M540 587l-269-473-271 473 540 0z" horiz-adv-x="540" />
<glyph glyph-name="left" unicode="&#xe801;" d="M0 350l148 149 352 351 148-148-351-352 351-352-148-148-352 352z" horiz-adv-x="648" />
<glyph glyph-name="down-circle" unicode="&#xe802;" d="M0 350q0 207 147 354t353 146 354-146 146-354-146-354-354-146-353 146-147 354z m248 55l252-377 252 377-135 0 0 267-234 0 0-267-135 0z" horiz-adv-x="1000" />
<glyph glyph-name="upload" unicode="&#xe803;" d="M0 84v73q0 33 24 56t57 24 56-24 24-56v-73q0-17 12-29t30-13h531q18 0 30 13t12 29v73q0 33 24 56t56 24 57-24 24-56v-73q0-84-59-143t-144-60h-531q-84 0-143 59t-60 144z m155 407q0 33 24 57l247 247q23 23 57 23 33 0 56-23l243-242q23-24 23-57t-23-57q-24-23-57-23t-57 23l-105 105v-344q0-33-24-57t-56-24q-33 0-57 24t-24 57v344l-110-110q-24-23-57-23t-56 23-24 57z" horiz-adv-x="937.5" />
......
No preview for this file type
......@@ -4,9 +4,9 @@ PTL.col = {
del: function($column) {
var $panel = $column.parent();
const $panel = $column.parent();
var nbOfColumnsInTab = $panel.find('.column').length;
const nbOfColumnsInTab = $panel.find('.column').length;
$column.hide('fast', function() {
......@@ -22,51 +22,51 @@ PTL.col = {
},
add: function(colIndex, newCol) {
var $colButtons = $('<div>')
const $colButtons = $('<div>')
.attr('class', 'buttons flex-box');
var $colLegend = $('<legend>')
const $colLegend = $('<legend>')
.attr('class', 'legend legend-col unique translate')
.data('title', 'Column')
.data('content', 'Column')
.text(PTL.tr('Column'));
var $srcLegend = $('<legend>')
.data('title', 'Feed')
.data('content', 'Feed')
.attr('class', 'legend unique translate')
.text(PTL.tr('Feed'));
var $column = $('<ul>')
.attr('class', 'column')
.append($colButtons);
var $srcNewButton = $('<button>')
.attr('title', PTL.tr('Add a feed to this column'))
.data('title', 'Add a feed to this column')
.attr('class', 'icon-plus unique translate new-feed-button button-column last')
.button()
.data('colIndex', colIndex);
var $colNewButton = $('<button>')
.attr('title', PTL.tr('Add a column'))
.data('title', 'Add a column')
.attr('class', 'icon-plus twin translate last')
.button()
.data('colIndex', colIndex);
var $colDelButton = $('<button>')
.attr('title', PTL.tr('Remove this column'))
.data('title', 'Remove this column')
.attr('class', 'col-del icon-minus twin')
.data('colIndex', colIndex)
.button();
const $srcLegend = $('<legend>')
.data('title', 'Feed')
.data('content', 'Feed')
.attr('class', 'legend unique translate')
.text(PTL.tr('Feed'));
const $column = $('<ul>')
.attr('class', 'column')
.append($colButtons);
const $srcNewButton = $('<button>')
.attr('title', PTL.tr('Add a feed to this column'))
.data('title', 'Add a feed to this column')
.attr('class', 'icon-plus unique translate new-feed-button button-column last')
.button()
.data('colIndex', colIndex);
const $colNewButton = $('<button>')
.attr('title', PTL.tr('Add a column'))
.data('title', 'Add a column')
.attr('class', 'icon-plus twin translate last')
.button()
.data('colIndex', colIndex);
const $colDelButton = $('<button>')
.attr('title', PTL.tr('Remove this column'))
.data('title', 'Remove this column')
.attr('class', 'col-del icon-minus twin')
.data('colIndex', colIndex)
.button();
$colDelButton.bind('click', function() {
var $column = $(this).parent().parent(),
$feedsInCol = $column.find('.feed'),
nbOfFeedsInCol = $feedsInCol.length;
const $column = $(this).parent().parent(),
$feedsInCol = $column.find('.feed'),
nbOfFeedsInCol = $feedsInCol.length;
if (nbOfFeedsInCol < 1) {
PTL.col.del($column);
......@@ -78,11 +78,11 @@ PTL.col = {
$colNewButton.bind('click', function() {
var $column = $(this).parent().parent(),
const $column = $(this).parent().parent(),
$panel = $column.parent(),
colIndex = $panel.find('.column').index($column);
var $newColumn = PTL.col.add(colIndex, true);
const $newColumn = PTL.col.add(colIndex, true);
$panel.find('button.col-del').removeClass('ui-state-disabled');
......@@ -102,15 +102,15 @@ PTL.col = {
helper: function (e, item) { //create custom helper
if (!item.hasClass('selected')) item.addClass('selected');
// clone selected items before hiding
var $elements = $('.selected').not('.ui-sortable-placeholder').clone();
const $elements = $('.selected').not('.ui-sortable-placeholder').clone();
//hide selected items
item.siblings('.selected').addClass('hidden');
var $helper = $('<ul class="feedHelper">');
const $helper = $('<ul class="feedHelper">');
return $helper.append($elements);
},
start: function (e, ui) {
// Drag begins
var $elements = ui.item.siblings('.selected.hidden').not('.ui-sortable-placeholder');
const $elements = ui.item.siblings('.selected.hidden').not('.ui-sortable-placeholder');
// Store the selected items to item being dragged
ui.item.data('items', $elements);
// Size the placeHolder
......
This diff is collapsed.
......@@ -4,15 +4,15 @@ PTL.feed = {
add:function($column, url, name, type, limit, status, iconhash, clickNew, isQueryString, progress) {
var feedIndex = $('#tabs').find('.feed').length;
const feedIndex = $('#tabs').find('.feed').length;
var $feed = $('<li>')
.attr('class', 'feed')
.data('url', url)
.data('name', name)
.data('type', type)
.data('limit', limit)
.data('iconhash', iconhash);
const $feed = $('<li>')
.attr('class', 'feed')
.data('url', url)
.data('name', name)
.data('type', type)
.data('limit', limit)
.data('iconhash', iconhash);
const $feedImg = $('<img>')
.attr({
......@@ -26,79 +26,85 @@ PTL.feed = {
$(this).attr('src', '/static/images/rss.gif');
});
var $feedIcon = $('<i>')
.attr('class', 'feed-control feedIcon translate')
.data('title', 'Fold / unfold this feed (%1)', url)
.attr('title', PTL.tr('Fold / unfold this feed (%1)', url))
.click(function() {
// Can't just use toggle because we have to pass the div to populate() in order to recreate it with the new data values, just setting data-* here doesn't work :(
if ($feedControls.data('status') == 'on') {
$(this).removeClass('fold')