Commit 31bd4e8e authored by yPhil's avatar yPhil
Browse files

camelCase everyWhere

parent e78196ec
Pipeline #409498 passed with stage
in 50 seconds
......@@ -7,8 +7,7 @@
## Conventions & style guide
- Pétrolette functions / methods, JQuery objects / vars are in `camelCase` ;
- HTML / CSS elements names are in `kebab-case` ;
- Pétrolette JS objects (funtions, var, ect.) and HTML / CSS elements names are in `camelCase` ;
- JQuery is aliased to `$`, and *all* JQuery vars & constants are sigil-prefixed with `$` ;
- Single quotes in JS, double quotes in HTML ;
- Indents: 2 *spaces*.
......
......@@ -11,32 +11,33 @@
## 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.
[Pétrolette](http://petrolette.space) is a news reading home page, [Free, Libre, and Open-Source Software](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.
Go ahead, it's yours : Create, delete, move feeds and tabs, close / quit your browser, come back tomorrow, everything is how you left it.
News feeds are organized into tabs, which can contain an infinite number of columns; everything is configurable, and saved directly in the browser [permanent cache](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage). To view the same feeds on your phone, either export / import your `petrolette.conf` file, or (recommended) use the synchronization feature with your personal ([Google Drive](https://www.google.com/drive/), [Dropbox](https://www.dropbox.com/), [5apps](https://5apps.com/storage) (much better), etc.) cloud.
News feeds are organized into tabs, which can contain an infinite number of columns; everything is configurable, and saved directly in the browser [permanent cache](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage). To view the same feeds on your phone, either export / import your `petrolette.conf` file, or (recommended) use the synchronization feature with your personal ([Google Drive](https://www.google.com/drive/), [Dropbox](https://www.dropbox.com/), [5apps](https://5apps.com/storage) (much better) etc.) cloud.
## Escape from GAFAM central
Pétrolette is fully self-contained, makes no external call whatsoever, and embarks a total of **zero tracker or "analysis" tool**. Needless to say it is also **completely ad-free**.
Pétrolette is fully self-contained, makes **no external call** whatsoever, and embarks a total of **zero tracker or "analysis" tool**. Needless to say she is also **completely ad-free** 😎
![Petrolette](https://framagit.org/yphil/assets/-/raw/master/img/petrolette.png)
## Features
- No ads, no trackers, just the news
- Displays all modern feeds (Atom, Media-RSS, etc.)
- Mobile / phone / tablet / responsive interface
- Search within the feeds
- No ads, no trackers, **just the news**
- Displays all modern feeds (Atom, RSS 1/2, **MediaRSS**, etc.)
- **Video / Audio playing** directly in Pétrolette
- **Discovery of the feed** in a page / website
- Direct link to the article / item's resources (image, video, sound) for opening in an external reader
- Advanced image management (download, gallery, slidewhow, etc.)
- Import / export of the feeds file (replace / merge)
- Online synchronization ([5apps](https://5apps.com/storage), [Google Drive](https://www.google.com/drive/), [Dropbox](https://www.dropbox.com/)) on all your devices
- *Search* feeds [example](https://search.modalogi.com/searx/search?categories=news&language=en-US&format=rss&q=zombie,attack) using a configurable search engine
- Bookmarklet for adding the feed of any website to Pétrolette on the fly
- Compatible with [FLOSS browsers](https://www.gnu.org/software/librejs/) (uncompressed executable files, licenses available directly and in standard format)
- Mobile / **phone** / tablet / responsive interface
- Direct link to all the article / item's resources (image, video, sound)
- **Search** within all the feeds
- Advanced image management (download, gallery, slideshow, etc.)
- Import / export of the tabs & feeds file (replace / merge)
- Online **synchronization** ([5apps](https://5apps.com/storage), [Google Drive](https://www.google.com/drive/), [Dropbox](https://www.dropbox.com/)) on all your devices
- Building of **Search terms feeds** [example](https://search.modalogi.com/searx/search?categories=news&language=en-US&format=rss&q=zombie,attack) using a configurable search engine
- Bookmarklet to **add the feed of any website to Pétrolette** on the fly
- Compatible with [FLOSS browsers](https://www.gnu.org/software/librejs/) (links to all uncompressed executable files, licenses available directly and in standard format)
## What's new?
......@@ -59,6 +60,11 @@ xdg-open http://localhost:8000
npm test
```
### Contributing
- Read the [development guidelines](https://framagit.org/yphil/petrolette/-/blob/master/DEV.md)
- Pétrolette is free software. However the development requires [a lot of time](https://www.youtube.com/watch?v=JlbMEx9H6FE) and [a lot of work](https://framagit.org/yphil/petrolette/-/commits/master). In order to keep developing it with new features I need your help ; Please consider to [support the Pétrolette project](https://liberapay.com/yPhil/donate) by sending a donation. Even the smallest amount will help a lot.
### LICENSE
This program is free software: you can redistribute it and/or modify
......
{
"name": "petrolette",
"description": "The news reader that doesn't know you",
"version": "1.2.8",
"version": "1.2.9A",
"private": true,
"license": "GPL-3.0-only",
"HTTP_PORT": 8000,
......
......@@ -453,15 +453,15 @@ li {
padding: 0;
}
#question-dialog > .flex-box {
#questionDialog > .flexBox {
flex-wrap: var(--wrap);
}
#dialog-blurb {
#dialogBlurb {
font-style: italic;
}
#dialog-text {
#dialogText {
flex-grow: 4; /* default 0 */
}
......@@ -858,7 +858,7 @@ button.ui-state-success {
}
.feed-header > .feed-handle,
.dialog .help-bookmarklet {
.dialog .helpBookmarklet {
cursor: -webkit-grab;
cursor: grab;
}
......@@ -924,13 +924,7 @@ ul.help li.cloud::before {
color: var(--color-ui-success);
}
/* label#killFeed:active { */
/* background: var(--color-ui-danger) !important; */
/* } */
.flex-box {
.flexBox {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
......@@ -940,11 +934,11 @@ ul.help li.cloud::before {
align-items: center;
}
#feed-guess-div {
#feedGuessDiv {
flex-wrap: nowrap;
}
.flex-box > label {
.flexBox > label {
flex: 1;
flex-grow: 4;
}
......@@ -1446,7 +1440,7 @@ div#logo-title > .logo-title > a:hover {
flex-direction: column;
}
#beggar-img {
#beggarImg {
max-width: 100%;
max-height: 100%;
}
......@@ -1456,25 +1450,6 @@ span.image-legend {
margin-bottom: 0.3em;
}
#beggar > span.beggarIcon {
font-size: 6em;
}
#beggar > span.beggarClose {
position: absolute;
top: 4px;
right: 6px;
cursor: pointer;
}
#beggar > span.beggarClose:hover {
color: var(--color-ui-sec);
}
#beggar > a.ui-button {
margin: 1em 0 0.5em 0;
}
#beggar > p {
color: var(--color-ui-pri);
margin: 0 0 0.2em 0;
......
......@@ -23,7 +23,7 @@ PTL.col = {
add: function(colIndex, newCol) {
const $colButtons = $('<div>')
.attr('class', 'buttons flex-box');
.attr('class', 'buttons flexBox');
const $colLegend = $('<legend>')
.attr('class', 'legend legend-col unique translate')
......
......@@ -7,7 +7,7 @@ PTL.dialog = {
},
resetTabs:function($button) {
$('#dialogs').load('/static/templates/dialogs.html #question-dialog', function() {
$('#dialogs').load('/static/templates/dialogs.html #questionDialog', function() {
const $dialog = $(this),
$icon = $dialog.find('div#icon > i');
......@@ -46,7 +46,7 @@ PTL.dialog = {
$dialog.find('h1')
.text(PTL.tr('Reset all tabs and feeds to defaults?'))
.next('p#dialog-blurb').addClass('dangerous')
.next('p#dialogBlurb').addClass('dangerous')
.text(PTL.tr('This action cannot be undone.'));
}
......@@ -61,7 +61,7 @@ PTL.dialog = {
},
beg:function() {
$('#dialogs').load('/static/templates/dialogs.html #beggar-dialog', function() {
$('#dialogs').load('/static/templates/dialogs.html #beggarDialog', function() {
const $dialog = $(this),
$beggarH4 = $dialog.find('h4'),
......@@ -123,15 +123,15 @@ PTL.dialog = {
},
about:function(versionNumber) {
$('#dialogs').load('/static/templates/dialogs.html #question-dialog', function() {
$('#dialogs').load('/static/templates/dialogs.html #questionDialog', function() {
const $dialog = $(this),
$content = $dialog.find('div.flex-box'),
$icon = $dialog.find('div#icon > i');
$content = $dialog.find('div.flexBox'),
$icon = $dialog.find('div#icon > i');
$icon.addClass('icon-petrolette');
$content.css('flex-direction', 'column');
$content.find('div#dialog-text').css('text-align', 'center'),
$content.find('div#dialogText').css('text-align', 'center'),
$dialog.dialog({
title: PTL.tr('About Pétrolette'),
......@@ -171,7 +171,7 @@ PTL.dialog = {
});
$dialog.find('h1').text('Pétrolette').addClass('logo-title')
.next('p#dialog-blurb').text(PTL.tr("The news page that doesn't know you"))
.next('p#dialogBlurb').text(PTL.tr("The news page that doesn't know you"))
.next('h2').text(versionNumber)
.next('p')
.append($('<a>')
......@@ -186,7 +186,7 @@ PTL.dialog = {
},
help:function() {
$('#dialogs').load('/static/templates/dialogs.html #help-dialog', function() {
$('#dialogs').load('/static/templates/dialogs.html #helpDialog', function() {
const $dialog = $(this);
......@@ -211,16 +211,16 @@ PTL.dialog = {
$(this).find('.help-button').button();
$(this).find('.help-bookmarklet')
$(this).find('.helpBookmarklet')
.attr('href', 'javascript:void(window.open("' + window.location.href + '?add=" + window.location.href))');
$('.help-tour').on('click', function() {
$('.helpTour').on('click', function() {
PTL.dialog.kill($dialog);
$('#tabs').tabs('option', 'active', 0);
PTL.util.help('ui');
});
$('.help-kb-shortcuts').on('click', function() {
$('.helpKbShortcuts').on('click', function() {
PTL.sideMenu('close');
PTL.dialog.kill($dialog);
PTL.dialog.kbShortcuts();
......@@ -242,7 +242,7 @@ PTL.dialog = {
$feedBody = $dataStore.parent().next('div.feed-body'),
allGroups = PTL.tab.list('all'),
$thisGroup = $feed.parent().parent(),
$groupMenu = $dialog.find('select#feed-tab-select'),
$groupMenu = $dialog.find('select#feedTabSelect'),
$spinner = $dialog.find('input#feedLimitSpinner').spinner({
classes: {
"ui-spinner": "shrink ui-corner-all"
......@@ -287,16 +287,16 @@ PTL.dialog = {
});
}
var newUrl = DOMPurify.sanitize($(this).find('input#feed-guess').val()),
newName = DOMPurify.sanitize($(this).find('input#feed-name').val()),
newType = $('#feed-type-div :radio:checked').attr('id');
var newUrl = DOMPurify.sanitize($(this).find('input#feedGuessInput').val()),
newName = DOMPurify.sanitize($(this).find('input#feedNameInput').val()),
newType = $('#feedTypeDiv :radio:checked').attr('id');
$dataStore
.data('url', newUrl)
.data('name', newName)
.data('type', newType);
if ($('input[name=kill-feed-check]:checked').val() === 'on') {
if ($('input[name=killFeedCheckbox]:checked').val() === 'on') {
$feed.hide('fade', 1000, function() {$feed.remove();});
} else {
PTL.feed.populate($button);
......@@ -353,10 +353,10 @@ PTL.dialog = {
$feedRefresh = $tabFeedId.find('.feedRefresh'),
$guessButton = $dialog.find('button#feedGuessButton').button(),
$guessSpinner = $dialog.find('button#feedGuessButton > i'),
$guessField = $dialog.find('input#feed-guess'),
$feedGuessInput = $dialog.find('input#feedGuessInput'),
$okButton = $dialog.find('.ui-dialog-buttonpane'),
$killFeedFieldset = $('fieldset#kill-feed'),
$killFeedLegend = $('legend#kill-feed-legend'),
$killFeedFieldset = $('fieldset#killFeedFieldset'),
$killFeedLegend = $('legend#killFeedLegend'),
$helpMiniButton = $('<button>'),
oldUrl = $dataStore.data('url'),
oldName = $dataStore.data('name'),
......@@ -387,7 +387,7 @@ PTL.dialog = {
$.get('/discover', {
dataType: 'json',
url: $guessField.val(),
url: $feedGuessInput.val(),
searchPrefix: PTL.prefs.readConfig('searchPrefix'),
timeout: 2000
}).fail(function(req, status, xhr) {
......@@ -395,7 +395,7 @@ PTL.dialog = {
}).done(function(feed) {
$guessSpinner.removeClass('spin icon-cog');
$guessField.val(feed);
$feedGuessInput.val(feed);
$guessSpinner
.removeClass('ui-state-error')
......@@ -411,8 +411,8 @@ PTL.dialog = {
});
$dialog.find('input#feed-guess').val(oldUrl);
$dialog.find('input#feed-name').val(oldName);
$feedGuessInput.val(oldUrl);
$dialog.find('input#feedNameInput').val(oldName);
$('input:radio, input:checkbox').checkboxradio({
icon: false
......@@ -458,7 +458,7 @@ PTL.dialog = {
},
killColumn:function($button) {
$('#dialogs').load('/static/templates/dialogs.html #question-dialog', function() {
$('#dialogs').load('/static/templates/dialogs.html #questionDialog', function() {
const $dialog = $(this),
$column = $button.parent().parent(),
......@@ -501,7 +501,7 @@ PTL.dialog = {
});
$dialog.find('h1').text(PTL.tr('Delete this column and all of its content?'))
.next('p#dialog-blurb')
.next('p#dialogBlurb')
.addClass('dangerous')
.text(PTL.tr('This action cannot be undone.'))
.next('h2').text(PTL.tr('Index'))
......@@ -517,7 +517,7 @@ PTL.dialog = {
},
kbShortcuts:function() {
$('#dialogs').load('/static/templates/dialogs.html #question-dialog', function() {
$('#dialogs').load('/static/templates/dialogs.html #questionDialog', function() {
const $dialog = $(this),
$iconDiv = $dialog.find('div#icon');
......@@ -630,7 +630,7 @@ PTL.dialog = {
.text(PTL.tr('Move focus to the next tab and immediately activate.'))));
$dialog.find('h1').text(PTL.tr('Keyboard shortcuts'))
.next('p#dialog-blurb').text(PTL.tr('A click on the Pétrolette logo puts the focus on the first tab'))
.next('p#dialogBlurb').text(PTL.tr('A click on the Pétrolette logo puts the focus on the first tab'))
.next('h2').text(PTL.tr('When focus is on a tab'))
.next('p')
.append($kbShortCutsTab)
......@@ -646,7 +646,7 @@ PTL.dialog = {
},
killTab:function($button) {
$('#dialogs').load('/static/templates/dialogs.html #question-dialog', function() {
$('#dialogs').load('/static/templates/dialogs.html #questionDialog', function() {
const $dialog = $(this),
$tabs = $('#tabs'),
......@@ -698,7 +698,7 @@ PTL.dialog = {
});
$dialog.find('h1').text(PTL.tr('Delete this tab and all of its content?'))
.next('p#dialog-blurb')
.next('p#dialogBlurb')
.addClass('dangerous')
.text(PTL.tr('This action cannot be undone.'))
.next('h2').text(PTL.tr('Name'))
......@@ -714,7 +714,7 @@ PTL.dialog = {
},
killFeed:function($button) {
$('#dialogs').load('/static/templates/dialogs.html #question-dialog', function() {
$('#dialogs').load('/static/templates/dialogs.html #questionDialog', function() {
const $dialog = $(this),
$thisFeed = $button.parent().parent().parent().parent(),
......@@ -756,7 +756,7 @@ PTL.dialog = {
});
$dialog.find('h1').text(PTL.tr('Delete this feed?'))
.next('p#dialog-blurb')
.next('p#dialogBlurb')
.addClass('dangerous')
.text(PTL.tr('This action cannot be undone.'))
.next('h2').text(PTL.tr('Name'))
......@@ -772,7 +772,7 @@ PTL.dialog = {
},
importFeeds:function(existingFeeds, importedFeedsFile) {
$('#dialogs').load('/static/templates/dialogs.html #question-dialog', function() {
$('#dialogs').load('/static/templates/dialogs.html #questionDialog', function() {
const $dialog = $(this),
$icon = $dialog.find('div#icon > i');
......@@ -820,7 +820,7 @@ PTL.dialog = {
});
$dialog.find('h1').text(PTL.tr('Open / import tabs and feeds'))
.next('p#dialog-blurb')
.next('p#dialogBlurb')
.addClass('dangerous')
.text(PTL.tr('This action cannot be undone.'))
.next('h2').text(PTL.tr('Replace or merge?'))
......@@ -836,7 +836,7 @@ PTL.dialog = {
},
editTab:function($tab) {
$('#dialogs').load('/static/templates/dialogs.html #edit-tab-dialog', function() {
$('#dialogs').load('/static/templates/dialogs.html #editTabDialog', function() {
const $dialog = $(this);
......@@ -857,7 +857,7 @@ PTL.dialog = {
title: PTL.tr('Ok'),
class: 'translate',
click: function() {
$('#' + $(this).data('tabId')).text($dialog.find('#tab-name').val());
$('#' + $(this).data('tabId')).text($dialog.find('#tabNameInput').val());
PTL.tab.saveTabs();
PTL.dialog.kill($dialog);
}
......@@ -869,7 +869,7 @@ PTL.dialog = {
PTL.dialog.kill($dialog);
});
const $tabName = $dialog.find('#tab-name');
const $tabNameInput = $dialog.find('input#tabNameInput');
const $tabNameLegend = $dialog.find('legend#tabNameLegend');
const $tabLeft = $dialog.find('button#left');
......@@ -887,10 +887,10 @@ PTL.dialog = {
$tabNameLegend.text(PTL.tr('Name'));
$tabName.val($(this).data('tabName'));
$tabNameInput.val($(this).data('tabName'));
$(this).on('submit', function () {
$('#' + $(this).data('tabId')).text($dialog.find('#tab-name').val());
$('#' + $(this).data('tabId')).text($tabNameInput.val());
PTL.tab.saveTabs();
PTL.dialog.kill($dialog);
});
......
......@@ -332,7 +332,7 @@ PTL.feed = {
.text(feedUrl);
const $errorButtonsFlexBox = $('<a>')
.attr('class', 'translate flex-box');
.attr('class', 'translate flexBox');
const $errorItem = $('<li>')
.attr('class', 'feed-item error')
......
......@@ -61,7 +61,7 @@ var PTL = (function() {
$('.js-enabled-only').show();
$('body').on('click','.help-bookmarklet', function(event) {
$('body').on('click','.helpBookmarklet', function(event) {
event.preventDefault();
});
......
......@@ -309,7 +309,7 @@ PTL.util = {
steps: [
{
title: PTL.tr('Everything starts here'),
element: 'input#feed-guess',
element: 'input#feedGuessInput',
intro: '<p><span class="translate" data-content="Enter a website address URL and click search, then OK, or simply enter the URL of the">' + PTL.tr('Enter a website address/URL and click search, then OK, or simply enter the URL of the') + '</span> <a class="help-rss ptl-link" href="https://' + PTL.language + '.wikipedia.org/wiki/RSS">' + PTL.tr('feed') + '</a>.</p><p><span class="translate" data-content="If what you enter is not a regular URL (an internet location in the form of \"http...\") Pétrolette will build a search feed using the words">' + PTL.tr('If what you enter is not a regular URL (an internet location in the form of \"http...\") Pétrolette will build a search feed using the words') + '.</span><p>',
position: 'bottom'
},
......@@ -321,25 +321,25 @@ PTL.util = {
},
{
title: PTL.tr('Feed name (optional)'),
element: 'input#feed-name',
element: 'input#feedNameInput',
intro: PTL.tr('Name the feed of this website, if it is not informative enough ; leave blank to get the default feed title.'),
position: 'left'
},
{
title: PTL.tr('Keep everything tidy'),
element: 'fieldset#feed-tab-fieldset',
element: 'fieldset#feedTabFieldset',
intro: PTL.tr('Move this feed to another tab.'),
position: 'bottom'
},
{
title: PTL.tr('Feed type'),
element: 'fieldset#feed-type-fieldset',
element: 'fieldset#feedTypeFieldset',
intro: PTL.tr('The type of feed: It can be all text, all image, or mixed.'),
position: 'top'
},
{
title: PTL.tr('Number of items'),
element: 'fieldset#feed-limit-fieldset',
element: 'fieldset#feedLimitFieldset',
intro: PTL.tr('How many new items should this feed display at a time?'),
position: 'top'
},
......
......@@ -3,9 +3,9 @@
<legend data-content="Location"
class="ui-corner-top translate">Location</legend>
<fieldset class="ui-widget ui-widget-content ui-corner-all">
<div id="feed-guess-div" class="flex-box">
<div id="feedGuessDiv" class="flexBox">
<input name="guess"
id="feed-guess"
id="feedGuessInput"
placeholder="https://xkcd.com/rss.xml or https://xkcd.com + SEARCH"
data-title="Enter a website address/URL and click search, then OK, or simply enter the URL of the feed and click OK"
value=""
......@@ -24,9 +24,9 @@
<legend data-content="Name"
class="ui-corner-top translate">Name</legend>
<fieldset class="ui-widget ui-widget-content ui-corner-all">
<div class="flex-box">
<div class="flexBox">
<input name="name"
id="feed-name"
id="feedNameInput"
placeholder="Feed name"
data-title="Feed name"
value=""
......@@ -40,9 +40,9 @@
class="ui-corner-top translate">
Tab
</legend>
<fieldset id="feed-tab-fieldset" class="ui-widget ui-widget-content ui-corner-all">
<fieldset id="feedTabFieldset" class="ui-widget ui-widget-content ui-corner-all">
<select id="feed-tab-select"
<select id="feedTabSelect"
class="unique translate"
name="Group"
data-title="Move the current feed to this tab">
......@@ -54,10 +54,10 @@
class="ui-corner-top translate">
Type
</legend>
<fieldset id="feed-type-fieldset" class="ui-widget ui-widget-content ui-corner-all">
<fieldset id="feedTypeFieldset" class="ui-widget ui-widget-content ui-corner-all">
<div id="feed-type-div"
class="flex-box">
<div id="feedTypeDiv"
class="flexBox">
<label data-title="Display only text"
data-content="Text"
......@@ -84,8 +84,8 @@
class="ui-corner-top translate">
Height
</legend>
<fieldset id="feed-limit-fieldset" class="ui-widget ui-widget-content ui-corner-all" data-title="Woyo!">
<div class="flex-box translate"
<fieldset id="feedLimitFieldset" class="ui-widget ui-widget-content ui-corner-all">
<div class="flexBox translate"
data-title="Number of items displayed in this feed">
<div class="feedLimit grow">
<label for="feedLimit" class="ui-helper-hidden">
......@@ -102,17 +102,17 @@
</fieldset>
<legend data-content="Delete"
id="kill-feed-legend"
id="killFeedLegend"
class="ui-corner-top translate">Delete
</legend>
<fieldset id="kill-feed" class="ui-widget ui-widget-content ui-corner-all">
<fieldset id="killFeedFieldset" class="ui-widget ui-widget-content ui-corner-all">
<div id="feedDel"
class="flex-box">
<input type="checkbox" name="kill-feed-check" id="kill-feed-check">
<label id="kill-feed"
class="flexBox">
<input type="checkbox" name="killFeedCheckbox" id="killFeedCheckbox">
<label id="killFeedLabel"
class="dangerous translate last"
for="kill-feed-check"
for="killFeedCheckbox"
data-title="Delete this feed"
data-content="Delete">
Delete this feed
......@@ -124,7 +124,7 @@
</form>
</div>
<div id="help-dialog" title="Pétrolette">
<div id="helpDialog" title="Pétrolette">
<h1 class="translate"
data-title="Welcome to Pétrolette"
data-content="Welcome to Pétrolette">Welcome to Pétrolette</h1>
......@@ -134,12 +134,12 @@
data-content="Pétrolette is a news reader focused on preventing anyone to know what you are reading.">
Pétrolette is a news reader focused on preventing anyone to know what you are reading.
</span>
<button class="help-tour translate ui-button ui-corner-all"