mirror of https://github.com/FreshRSS/FreshRSS.git
Merge branch 'edge' into feature/modernize-code-to-php74
This commit is contained in:
commit
8a13a378df
|
@ -21,7 +21,7 @@ Example for Linux Debian / Ubuntu:
|
|||
|
||||
```sh
|
||||
# Install default Docker Compose and automatically the corresponding version of Docker
|
||||
apt install docker-compose
|
||||
apt install docker-compose-v2
|
||||
```
|
||||
|
||||
## Quick run
|
||||
|
@ -194,6 +194,8 @@ docker run -d --restart unless-stopped --log-opt max-size=10m \
|
|||
|
||||
In the FreshRSS setup, you will then specify the name of the container (`freshrss-db`) as the host for the database.
|
||||
|
||||
See also the section [Docker Compose with PostgreSQL](#docker-compose-with-postgresql) below.
|
||||
|
||||
### [MySQL](https://hub.docker.com/_/mysql/) or [MariaDB](https://hub.docker.com/_/mariadb)
|
||||
|
||||
```sh
|
||||
|
@ -285,13 +287,13 @@ See [`docker-compose.yml`](./freshrss/docker-compose.yml)
|
|||
```sh
|
||||
cd ./FreshRSS/Docker/freshrss/
|
||||
# Update
|
||||
docker-compose pull
|
||||
docker compose pull
|
||||
# Run
|
||||
docker-compose -f docker-compose.yml -f docker-compose-local.yml up -d --remove-orphans
|
||||
docker compose -f docker-compose.yml -f docker-compose-local.yml up -d --remove-orphans
|
||||
# Logs
|
||||
docker-compose logs -f --timestamps
|
||||
docker compose logs -f --timestamps
|
||||
# Stop
|
||||
docker-compose down --remove-orphans
|
||||
docker compose down --remove-orphans
|
||||
```
|
||||
|
||||
Detailed (partial) example of Docker Compose for FreshRSS:
|
||||
|
@ -378,13 +380,15 @@ See [`docker-compose-db.yml`](./freshrss/docker-compose-db.yml)
|
|||
```sh
|
||||
cd ./FreshRSS/Docker/freshrss/
|
||||
# Update
|
||||
docker-compose -f docker-compose.yml -f docker-compose-db.yml pull
|
||||
docker compose -f docker-compose.yml -f docker-compose-db.yml pull
|
||||
# Run
|
||||
docker-compose -f docker-compose.yml -f docker-compose-db.yml -f docker-compose-local.yml up -d --remove-orphans
|
||||
docker compose -f docker-compose.yml -f docker-compose-db.yml -f docker-compose-local.yml up -d --remove-orphans
|
||||
# Logs
|
||||
docker-compose -f docker-compose.yml -f docker-compose-db.yml logs -f --timestamps
|
||||
docker compose -f docker-compose.yml -f docker-compose-db.yml logs -f --timestamps
|
||||
```
|
||||
|
||||
See also the section [Migrate database](#migrate-database) below to upgrade to a major PostgreSQL version with Docker Compose.
|
||||
|
||||
### Docker Compose for development
|
||||
|
||||
Use the local (git) FreshRSS source code instead of the one inside the Docker container,
|
||||
|
@ -396,11 +400,11 @@ See [`docker-compose-development.yml`](./freshrss/docker-compose-development.yml
|
|||
cd ./FreshRSS/Docker/freshrss/
|
||||
# Update
|
||||
git pull --ff-only --prune
|
||||
docker-compose pull
|
||||
docker compose pull
|
||||
# Run
|
||||
docker-compose -f docker-compose-development.yml -f docker-compose.yml -f docker-compose-local.yml up --remove-orphans
|
||||
docker compose -f docker-compose-development.yml -f docker-compose.yml -f docker-compose-local.yml up --remove-orphans
|
||||
# Stop with [Control]+[C] and purge
|
||||
docker-compose down --remove-orphans --volumes
|
||||
docker compose down --remove-orphans --volumes
|
||||
```
|
||||
|
||||
> ℹ️ You can combine it with `-f docker-compose-db.yml` to spin a PostgreSQL database.
|
||||
|
@ -446,13 +450,13 @@ See [`docker-compose-proxy.yml`](./freshrss/docker-compose-proxy.yml)
|
|||
```sh
|
||||
cd ./FreshRSS/Docker/freshrss/
|
||||
# Update
|
||||
docker-compose -f docker-compose.yml -f docker-compose-proxy.yml pull
|
||||
docker compose -f docker-compose.yml -f docker-compose-proxy.yml pull
|
||||
# Run
|
||||
docker-compose -f docker-compose.yml -f docker-compose-proxy.yml up -d --remove-orphans
|
||||
docker compose -f docker-compose.yml -f docker-compose-proxy.yml up -d --remove-orphans
|
||||
# Logs
|
||||
docker-compose -f docker-compose.yml -f docker-compose-proxy.yml logs -f --timestamps
|
||||
docker compose -f docker-compose.yml -f docker-compose-proxy.yml logs -f --timestamps
|
||||
# Stop
|
||||
docker-compose -f docker-compose.yml -f docker-compose-proxy.yml down --remove-orphans
|
||||
docker compose -f docker-compose.yml -f docker-compose-proxy.yml down --remove-orphans
|
||||
```
|
||||
|
||||
> ℹ️ You can combine it with `-f docker-compose-db.yml` to spin a PostgreSQL database.
|
||||
|
@ -650,3 +654,46 @@ docker run -d --restart unless-stopped --log-opt max-size=10m \
|
|||
--name freshrss_cron freshrss/freshrss:alpine \
|
||||
crond -f -d 6
|
||||
```
|
||||
|
||||
## Migrate database
|
||||
|
||||
Our [CLI](../cli/README.md) offers commands to back-up and migrate user databases,
|
||||
with `cli/db-backup.php` and `cli/db-restore.php` in particular.
|
||||
|
||||
Here is an example (assuming our [Docker Compose example](#docker-compose-with-postgresql))
|
||||
intended for migrating to a newer major version of PostgreSQL,
|
||||
but which can also be used to migrate between other databases (e.g. MySQL to PostgreSQL).
|
||||
|
||||
```sh
|
||||
# Stop FreshRSS container (Web server + cron) during maintenance
|
||||
docker compose down freshrss
|
||||
|
||||
# Optional additional pre-upgrade back-up using PostgreSQL own mechanism
|
||||
docker compose -f docker-compose-db.yml \
|
||||
exec freshrss-db pg_dump -U freshrss freshrss | gzip -9 > freshrss-postgres-backup.sql.gz
|
||||
# ------↑ Name of your PostgreSQL Docker container
|
||||
# -----------------------------↑ Name of your PostgreSQL user for FreshRSS
|
||||
# --------------------------------------↑ Name of your PostgreSQL database for FreshRSS
|
||||
|
||||
# Back-up all users’ respective tables to SQLite files
|
||||
docker compose -f docker-compose.yml -f docker-compose-db.yml \
|
||||
run --rm freshrss cli/db-backup.php
|
||||
|
||||
# Remove old database (PostgreSQL) container and its data volume
|
||||
docker compose -f docker-compose-db.yml \
|
||||
down --volumes freshrss-db
|
||||
|
||||
# Edit your Compose file to use new database (e.g. newest postgres:xx)
|
||||
nano docker-compose-db.yml
|
||||
|
||||
# Start new database (PostgreSQL) container and its new empty data volume
|
||||
docker compose -f docker-compose.yml -f docker-compose-db.yml \
|
||||
up -d freshrss-db
|
||||
|
||||
# Restore all users’ respective tables from SQLite files
|
||||
docker compose -f docker-compose.yml -f docker-compose-db.yml \
|
||||
run --rm freshrss cli/db-restore.php --delete-backup
|
||||
|
||||
# Restart a new FreshRSS container after maintenance
|
||||
docker compose -f docker-compose.yml -f docker-compose-db.yml up -d freshrss
|
||||
```
|
||||
|
|
|
@ -259,7 +259,7 @@ class FreshRSS_feed_Controller extends FreshRSS_ActionController {
|
|||
if (!empty($xPathSettings)) {
|
||||
$attributes['xpath'] = $xPathSettings;
|
||||
}
|
||||
} elseif ($feed_kind === FreshRSS_Feed::KIND_JSON_DOTPATH) {
|
||||
} elseif ($feed_kind === FreshRSS_Feed::KIND_JSON_DOTNOTATION) {
|
||||
$jsonSettings = [];
|
||||
if (Minz_Request::paramString('jsonFeedTitle') !== '') {
|
||||
$jsonSettings['feedTitle'] = Minz_Request::paramString('jsonFeedTitle', true);
|
||||
|
@ -295,7 +295,7 @@ class FreshRSS_feed_Controller extends FreshRSS_ActionController {
|
|||
$jsonSettings['itemUid'] = Minz_Request::paramString('jsonItemUid', true);
|
||||
}
|
||||
if (!empty($jsonSettings)) {
|
||||
$attributes['json_dotpath'] = $jsonSettings;
|
||||
$attributes['json_dotnotation'] = $jsonSettings;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -495,10 +495,10 @@ class FreshRSS_feed_Controller extends FreshRSS_ActionController {
|
|||
if ($simplePie === null) {
|
||||
throw new FreshRSS_Feed_Exception('XML+XPath parsing failed for [' . $feed->url(false) . ']');
|
||||
}
|
||||
} elseif ($feed->kind() === FreshRSS_Feed::KIND_JSON_DOTPATH) {
|
||||
} elseif ($feed->kind() === FreshRSS_Feed::KIND_JSON_DOTNOTATION) {
|
||||
$simplePie = $feed->loadJson();
|
||||
if ($simplePie === null) {
|
||||
throw new FreshRSS_Feed_Exception('JSON dotpath parsing failed for [' . $feed->url(false) . ']');
|
||||
throw new FreshRSS_Feed_Exception('JSON dot notation parsing failed for [' . $feed->url(false) . ']');
|
||||
}
|
||||
} elseif ($feed->kind() === FreshRSS_Feed::KIND_JSONFEED) {
|
||||
$simplePie = $feed->loadJson();
|
||||
|
@ -571,6 +571,7 @@ class FreshRSS_feed_Controller extends FreshRSS_ActionController {
|
|||
$existingHash = $existingHashForGuids[$entry->guid()];
|
||||
if (strcasecmp($existingHash, $entry->hash()) !== 0) {
|
||||
//This entry already exists but has been updated
|
||||
$entry->_isUpdated(true);
|
||||
//Minz_Log::debug('Entry with GUID `' . $entry->guid() . '` updated in feed ' . $feed->url(false) .
|
||||
//', old hash ' . $existingHash . ', new hash ' . $entry->hash());
|
||||
$entry->_isFavorite(null); // Do not change favourite state
|
||||
|
@ -585,6 +586,11 @@ class FreshRSS_feed_Controller extends FreshRSS_ActionController {
|
|||
continue;
|
||||
}
|
||||
|
||||
$entry->applyFilterActions($titlesAsRead);
|
||||
if ($readWhenSameTitleInFeed > 0) {
|
||||
$titlesAsRead[$entry->title()] = true;
|
||||
}
|
||||
|
||||
if (!$entry->isRead()) {
|
||||
$needFeedCacheRefresh = true; //Maybe
|
||||
$nbMarkedUnread++;
|
||||
|
@ -599,6 +605,7 @@ class FreshRSS_feed_Controller extends FreshRSS_ActionController {
|
|||
$entryDAO->updateEntry($entry->toArray());
|
||||
}
|
||||
} else {
|
||||
$entry->_isUpdated(false);
|
||||
$id = uTimeString();
|
||||
$entry->_id($id);
|
||||
|
||||
|
|
|
@ -238,7 +238,7 @@ class FreshRSS_subscription_Controller extends FreshRSS_ActionController {
|
|||
$xPathSettings['itemUid'] = Minz_Request::paramString('xPathItemUid', true);
|
||||
if (!empty($xPathSettings))
|
||||
$feed->_attribute('xpath', $xPathSettings);
|
||||
} elseif ($feed->kind() === FreshRSS_Feed::KIND_JSON_DOTPATH) {
|
||||
} elseif ($feed->kind() === FreshRSS_Feed::KIND_JSON_DOTNOTATION) {
|
||||
$jsonSettings = [];
|
||||
if (Minz_Request::paramString('jsonFeedTitle') !== '') {
|
||||
$jsonSettings['feedTitle'] = Minz_Request::paramString('jsonFeedTitle', true);
|
||||
|
@ -274,7 +274,7 @@ class FreshRSS_subscription_Controller extends FreshRSS_ActionController {
|
|||
$jsonSettings['itemUid'] = Minz_Request::paramString('jsonItemUid', true);
|
||||
}
|
||||
if (!empty($jsonSettings)) {
|
||||
$feed->_attribute('json_dotpath', $jsonSettings);
|
||||
$feed->_attribute('json_dotnotation', $jsonSettings);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -48,6 +48,18 @@ class FreshRSS_DatabaseDAO extends Minz_ModelPdo {
|
|||
}
|
||||
}
|
||||
|
||||
public function exits(): bool {
|
||||
$sql = 'SELECT * FROM `_entry` LIMIT 1';
|
||||
$stm = $this->pdo->query($sql);
|
||||
if ($stm !== false) {
|
||||
$res = $stm->fetchAll(PDO::FETCH_COLUMN, 0);
|
||||
if ($res !== false) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function tablesAreCorrect(): bool {
|
||||
$res = $this->fetchAssoc('SHOW TABLES');
|
||||
if ($res == null) {
|
||||
|
@ -242,6 +254,7 @@ SQL;
|
|||
}
|
||||
$error = '';
|
||||
|
||||
$databaseDAO = FreshRSS_Factory::createDatabaseDAO();
|
||||
$userDAO = FreshRSS_Factory::createUserDao();
|
||||
$catDAO = FreshRSS_Factory::createCategoryDao();
|
||||
$feedDAO = FreshRSS_Factory::createFeedDao();
|
||||
|
@ -259,15 +272,18 @@ SQL;
|
|||
$error = 'Error: SQLite import file is not readable: ' . $filename;
|
||||
} elseif ($clearFirst) {
|
||||
$userDAO->deleteUser();
|
||||
$userDAO = FreshRSS_Factory::createUserDao();
|
||||
if ($this->pdo->dbType() === 'sqlite') {
|
||||
//We cannot just delete the .sqlite file otherwise PDO gets buggy.
|
||||
//SQLite is the only one with database-level optimization, instead of at table level.
|
||||
$this->optimize();
|
||||
}
|
||||
} else {
|
||||
$nbEntries = $entryDAO->countUnreadRead();
|
||||
if (!empty($nbEntries['all'])) {
|
||||
$error = 'Error: Destination database already contains some entries!';
|
||||
if ($databaseDAO->exits()) {
|
||||
$nbEntries = $entryDAO->countUnreadRead();
|
||||
if (isset($nbEntries['all']) && $nbEntries['all'] > 0) {
|
||||
$error = 'Error: Destination database already contains some entries!';
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -24,6 +24,7 @@ class FreshRSS_Entry extends Minz_Model {
|
|||
private string $hash = '';
|
||||
private ?bool $is_read;
|
||||
private ?bool $is_favorite;
|
||||
private bool $is_updated = false;
|
||||
private int $feedId;
|
||||
private ?FreshRSS_Feed $feed;
|
||||
/** @var array<string> */
|
||||
|
@ -394,6 +395,18 @@ HTML;
|
|||
return $this->is_favorite;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the entry has been modified since it was inserted in database.
|
||||
* @returns bool `true` if the entry already existed (and has been modified), `false` if the entry is new (or unmodified).
|
||||
*/
|
||||
public function isUpdated(): ?bool {
|
||||
return $this->is_updated;
|
||||
}
|
||||
|
||||
public function _isUpdated(bool $value): void {
|
||||
$this->is_updated = $value;
|
||||
}
|
||||
|
||||
public function feed(): ?FreshRSS_Feed {
|
||||
if ($this->feed === null) {
|
||||
$feedDAO = FreshRSS_Factory::createFeedDao();
|
||||
|
|
|
@ -31,7 +31,7 @@ class FreshRSS_Feed extends Minz_Model {
|
|||
public const KIND_JSON_XPATH = 20;
|
||||
|
||||
public const KIND_JSONFEED = 25;
|
||||
public const KIND_JSON_DOTPATH = 30;
|
||||
public const KIND_JSON_DOTNOTATION = 30;
|
||||
|
||||
public const PRIORITY_IMPORTANT = 20;
|
||||
public const PRIORITY_MAIN_STREAM = 10;
|
||||
|
@ -621,7 +621,7 @@ class FreshRSS_Feed extends Minz_Model {
|
|||
}
|
||||
|
||||
/** @return array<string,string> */
|
||||
private function dotPathsForStandardJsonFeed(): array {
|
||||
private function dotNotationForStandardJsonFeed(): array {
|
||||
return [
|
||||
'feedTitle' => 'title',
|
||||
'item' => 'items',
|
||||
|
@ -662,11 +662,11 @@ class FreshRSS_Feed extends Minz_Model {
|
|||
return null;
|
||||
}
|
||||
|
||||
/** @var array<string,string> $json_dotpath */
|
||||
$json_dotpath = $this->attributeArray('json_dotpath') ?? [];
|
||||
$dotPaths = $this->kind() === FreshRSS_Feed::KIND_JSONFEED ? $this->dotPathsForStandardJsonFeed() : $json_dotpath;
|
||||
/** @var array<string,string> $json_dotnotation */
|
||||
$json_dotnotation = $this->attributeArray('json_dotnotation') ?? [];
|
||||
$dotnotations = $this->kind() === FreshRSS_Feed::KIND_JSONFEED ? $this->dotNotationForStandardJsonFeed() : $json_dotnotation;
|
||||
|
||||
$feedContent = FreshRSS_dotNotation_Util::convertJsonToRss($jf, $feedSourceUrl, $dotPaths, $this->name());
|
||||
$feedContent = FreshRSS_dotNotation_Util::convertJsonToRss($jf, $feedSourceUrl, $dotnotations, $this->name());
|
||||
if ($feedContent == null) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -133,10 +133,16 @@ trait FreshRSS_FilterActionsTrait {
|
|||
}
|
||||
break;
|
||||
case 'star':
|
||||
$entry->_isFavorite(true);
|
||||
if (!$entry->isUpdated()) {
|
||||
// Do not apply to updated articles, to avoid overruling a user manual action
|
||||
$entry->_isFavorite(true);
|
||||
}
|
||||
break;
|
||||
case 'label':
|
||||
$applyLabel = true;
|
||||
if (!$entry->isUpdated()) {
|
||||
// Do not apply to updated articles, to avoid overruling a user manual action
|
||||
$applyLabel = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,8 @@ class FreshRSS_Export_Service {
|
|||
public const TYPE_HTML_XPATH = 'HTML+XPath';
|
||||
public const TYPE_XML_XPATH = 'XML+XPath';
|
||||
public const TYPE_RSS_ATOM = 'rss';
|
||||
public const TYPE_JSON_DOTPATH = 'JSON+DotPath';
|
||||
public const TYPE_JSON_DOTPATH = 'JSON+DotPath'; // Legacy 1.24.0-dev
|
||||
public const TYPE_JSON_DOTNOTATION = 'JSON+DotNotation';
|
||||
public const TYPE_JSONFEED = 'JSONFeed';
|
||||
|
||||
/**
|
||||
|
@ -153,7 +154,7 @@ class FreshRSS_Export_Service {
|
|||
$zip_filename = 'freshrss_' . $this->username . '_' . $day . '_export.zip';
|
||||
|
||||
// From https://stackoverflow.com/questions/1061710/php-zip-files-on-the-fly
|
||||
$zip_file = tempnam('/tmp', 'zip');
|
||||
$zip_file = tempnam(TMP_PATH, 'zip');
|
||||
if ($zip_file == false) {
|
||||
return [$zip_filename, false];
|
||||
}
|
||||
|
|
|
@ -161,8 +161,9 @@ class FreshRSS_Import_Service {
|
|||
case strtolower(FreshRSS_Export_Service::TYPE_XML_XPATH):
|
||||
$feed->_kind(FreshRSS_Feed::KIND_XML_XPATH);
|
||||
break;
|
||||
case strtolower(FreshRSS_Export_Service::TYPE_JSON_DOTNOTATION):
|
||||
case strtolower(FreshRSS_Export_Service::TYPE_JSON_DOTPATH):
|
||||
$feed->_kind(FreshRSS_Feed::KIND_JSON_DOTPATH);
|
||||
$feed->_kind(FreshRSS_Feed::KIND_JSON_DOTNOTATION);
|
||||
break;
|
||||
case strtolower(FreshRSS_Export_Service::TYPE_JSONFEED):
|
||||
$feed->_kind(FreshRSS_Feed::KIND_JSONFEED);
|
||||
|
@ -254,7 +255,7 @@ class FreshRSS_Import_Service {
|
|||
$jsonSettings['itemUid'] = $feed_elt['frss:jsonItemUid'];
|
||||
}
|
||||
if (!empty($jsonSettings)) {
|
||||
$feed->_attribute('json_dotpath', $jsonSettings);
|
||||
$feed->_attribute('json_dotnotation', $jsonSettings);
|
||||
}
|
||||
|
||||
$curl_params = [];
|
||||
|
|
|
@ -97,11 +97,11 @@ final class FreshRSS_dotNotation_Util
|
|||
*
|
||||
* @param array<string> $jf json feed
|
||||
* @param string $feedSourceUrl the source URL for the feed
|
||||
* @param array<string,string> $dotPaths dot paths to map JSON into RSS
|
||||
* @param string $defaultRssTitle Default title of the RSS feed, if not already provided in dotPath `feedTitle`
|
||||
* @param array<string,string> $dotNotation dot notation to map JSON into RSS
|
||||
* @param string $defaultRssTitle Default title of the RSS feed, if not already provided in dotNotation `feedTitle`
|
||||
*/
|
||||
public static function convertJsonToRss(array $jf, string $feedSourceUrl, array $dotPaths, string $defaultRssTitle = ''): ?string {
|
||||
if (!isset($dotPaths['item']) || $dotPaths['item'] === '') {
|
||||
public static function convertJsonToRss(array $jf, string $feedSourceUrl, array $dotNotation, string $defaultRssTitle = ''): ?string {
|
||||
if (!isset($dotNotation['item']) || $dotNotation['item'] === '') {
|
||||
return null; //no definition of item path, but we can't scrape anything without knowing this
|
||||
}
|
||||
|
||||
|
@ -112,40 +112,40 @@ final class FreshRSS_dotNotation_Util
|
|||
$view->html_url = $view->rss_url;
|
||||
$view->entries = [];
|
||||
|
||||
$view->rss_title = isset($dotPaths['feedTitle'])
|
||||
? (htmlspecialchars(FreshRSS_dotNotation_Util::getString($jf, $dotPaths['feedTitle']) ?? '', ENT_COMPAT, 'UTF-8') ?: $defaultRssTitle)
|
||||
$view->rss_title = isset($dotNotation['feedTitle'])
|
||||
? (htmlspecialchars(FreshRSS_dotNotation_Util::getString($jf, $dotNotation['feedTitle']) ?? '', ENT_COMPAT, 'UTF-8') ?: $defaultRssTitle)
|
||||
: $defaultRssTitle;
|
||||
|
||||
$jsonItems = FreshRSS_dotNotation_Util::get($jf, $dotPaths['item']);
|
||||
$jsonItems = FreshRSS_dotNotation_Util::get($jf, $dotNotation['item']);
|
||||
if (!is_array($jsonItems) || count($jsonItems) === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($jsonItems as $jsonItem) {
|
||||
$rssItem = [];
|
||||
$rssItem['link'] = isset($dotPaths['itemUri']) ? FreshRSS_dotNotation_Util::getString($jsonItem, $dotPaths['itemUri']) ?? '' : '';
|
||||
$rssItem['link'] = isset($dotNotation['itemUri']) ? FreshRSS_dotNotation_Util::getString($jsonItem, $dotNotation['itemUri']) ?? '' : '';
|
||||
if (empty($rssItem['link'])) {
|
||||
continue;
|
||||
}
|
||||
$rssItem['title'] = isset($dotPaths['itemTitle']) ? FreshRSS_dotNotation_Util::getString($jsonItem, $dotPaths['itemTitle']) ?? '' : '';
|
||||
$rssItem['author'] = isset($dotPaths['itemAuthor']) ? FreshRSS_dotNotation_Util::getString($jsonItem, $dotPaths['itemAuthor']) ?? '' : '';
|
||||
$rssItem['timestamp'] = isset($dotPaths['itemTimestamp']) ? FreshRSS_dotNotation_Util::getString($jsonItem, $dotPaths['itemTimestamp']) ?? '' : '';
|
||||
$rssItem['title'] = isset($dotNotation['itemTitle']) ? FreshRSS_dotNotation_Util::getString($jsonItem, $dotNotation['itemTitle']) ?? '' : '';
|
||||
$rssItem['author'] = isset($dotNotation['itemAuthor']) ? FreshRSS_dotNotation_Util::getString($jsonItem, $dotNotation['itemAuthor']) ?? '' : '';
|
||||
$rssItem['timestamp'] = isset($dotNotation['itemTimestamp']) ? FreshRSS_dotNotation_Util::getString($jsonItem, $dotNotation['itemTimestamp']) ?? '' : '';
|
||||
|
||||
//get simple content, but if a path for HTML content has been provided, replace the simple content with HTML content
|
||||
$rssItem['content'] = isset($dotPaths['itemContent']) ? FreshRSS_dotNotation_Util::getString($jsonItem, $dotPaths['itemContent']) ?? '' : '';
|
||||
$rssItem['content'] = isset($dotPaths['itemContentHTML'])
|
||||
? FreshRSS_dotNotation_Util::getString($jsonItem, $dotPaths['itemContentHTML']) ?? ''
|
||||
$rssItem['content'] = isset($dotNotation['itemContent']) ? FreshRSS_dotNotation_Util::getString($jsonItem, $dotNotation['itemContent']) ?? '' : '';
|
||||
$rssItem['content'] = isset($dotNotation['itemContentHTML'])
|
||||
? FreshRSS_dotNotation_Util::getString($jsonItem, $dotNotation['itemContentHTML']) ?? ''
|
||||
: $rssItem['content'];
|
||||
|
||||
if (isset($dotPaths['itemTimeFormat']) && is_string($dotPaths['itemTimeFormat'])) {
|
||||
$dateTime = DateTime::createFromFormat($dotPaths['itemTimeFormat'], $rssItem['timestamp']);
|
||||
if (isset($dotNotation['itemTimeFormat']) && is_string($dotNotation['itemTimeFormat'])) {
|
||||
$dateTime = DateTime::createFromFormat($dotNotation['itemTimeFormat'], $rssItem['timestamp']);
|
||||
if ($dateTime != false) {
|
||||
$rssItem['timestamp'] = $dateTime->format(DateTime::ATOM);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($dotPaths['itemCategories'])) {
|
||||
$jsonItemCategories = FreshRSS_dotNotation_Util::get($jsonItem, $dotPaths['itemCategories']);
|
||||
if (isset($dotNotation['itemCategories'])) {
|
||||
$jsonItemCategories = FreshRSS_dotNotation_Util::get($jsonItem, $dotNotation['itemCategories']);
|
||||
if (is_string($jsonItemCategories) && $jsonItemCategories !== '') {
|
||||
$rssItem['tags'] = [$jsonItemCategories];
|
||||
} elseif (is_array($jsonItemCategories) && count($jsonItemCategories) > 0) {
|
||||
|
@ -158,31 +158,31 @@ final class FreshRSS_dotNotation_Util
|
|||
}
|
||||
}
|
||||
|
||||
$rssItem['thumbnail'] = isset($dotPaths['itemThumbnail']) ? FreshRSS_dotNotation_Util::getString($jsonItem, $dotPaths['itemThumbnail']) ?? '' : '';
|
||||
$rssItem['thumbnail'] = isset($dotNotation['itemThumbnail']) ? FreshRSS_dotNotation_Util::getString($jsonItem, $dotNotation['itemThumbnail']) ?? '' : '';
|
||||
|
||||
//Enclosures?
|
||||
if (isset($dotPaths['itemAttachment'])) {
|
||||
$jsonItemAttachments = FreshRSS_dotNotation_Util::get($jsonItem, $dotPaths['itemAttachment']);
|
||||
if (isset($dotNotation['itemAttachment'])) {
|
||||
$jsonItemAttachments = FreshRSS_dotNotation_Util::get($jsonItem, $dotNotation['itemAttachment']);
|
||||
if (is_array($jsonItemAttachments) && count($jsonItemAttachments) > 0) {
|
||||
$rssItem['attachments'] = [];
|
||||
foreach ($jsonItemAttachments as $attachment) {
|
||||
$rssAttachment = [];
|
||||
$rssAttachment['url'] = isset($dotPaths['itemAttachmentUrl'])
|
||||
? FreshRSS_dotNotation_Util::getString($attachment, $dotPaths['itemAttachmentUrl'])
|
||||
$rssAttachment['url'] = isset($dotNotation['itemAttachmentUrl'])
|
||||
? FreshRSS_dotNotation_Util::getString($attachment, $dotNotation['itemAttachmentUrl'])
|
||||
: '';
|
||||
$rssAttachment['type'] = isset($dotPaths['itemAttachmentType'])
|
||||
? FreshRSS_dotNotation_Util::getString($attachment, $dotPaths['itemAttachmentType'])
|
||||
$rssAttachment['type'] = isset($dotNotation['itemAttachmentType'])
|
||||
? FreshRSS_dotNotation_Util::getString($attachment, $dotNotation['itemAttachmentType'])
|
||||
: '';
|
||||
$rssAttachment['length'] = isset($dotPaths['itemAttachmentLength'])
|
||||
? FreshRSS_dotNotation_Util::get($attachment, $dotPaths['itemAttachmentLength'])
|
||||
$rssAttachment['length'] = isset($dotNotation['itemAttachmentLength'])
|
||||
? FreshRSS_dotNotation_Util::get($attachment, $dotNotation['itemAttachmentLength'])
|
||||
: '';
|
||||
$rssItem['attachments'][] = $rssAttachment;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($dotPaths['itemUid'])) {
|
||||
$rssItem['guid'] = FreshRSS_dotNotation_Util::getString($jsonItem, $dotPaths['itemUid']);
|
||||
if (isset($dotNotation['itemUid'])) {
|
||||
$rssItem['guid'] = FreshRSS_dotNotation_Util::getString($jsonItem, $dotNotation['itemUid']);
|
||||
}
|
||||
|
||||
if (empty($rssItem['guid'])) {
|
||||
|
|
|
@ -19,8 +19,8 @@ return array(
|
|||
'http' => 'HTTP (haladó felhasználóknak HTTPS-el)',
|
||||
'none' => 'nincs (veszélyes)',
|
||||
'title' => 'Hitelesítés',
|
||||
'token' => 'Master authentication token', // TODO
|
||||
'token_help' => 'Allows access to all RSS outputs of the user as well as refreshing feeds without authentication:', // TODO
|
||||
'token' => 'Fő hitelesítési token',
|
||||
'token_help' => 'Lehetővé teszi a hozzáférést a felhasználó összes RSS-kimenetéhez, valamint a hírfolyamok frissítéséhez hitelesítés nélkül:',
|
||||
'type' => 'Hitelesítési módszer',
|
||||
'unsafe_autologin' => 'Engedélyezze a nem biztonságos automata bejelentkezést a következő formátummal: ',
|
||||
),
|
||||
|
|
|
@ -120,20 +120,20 @@ return array(
|
|||
'feeds' => 'Rendezés hírforrás szerint',
|
||||
'order' => 'Rendezés dátum szerint',
|
||||
'search' => 'Kifejezés',
|
||||
'shareOpml' => 'Enable sharing by OPML of corresponding categories and feeds', // TODO
|
||||
'shareRss' => 'Enable sharing by HTML & RSS', // TODO
|
||||
'shareOpml' => 'Engedélyezze a megfelelő kategóriák és hírcsatornák OPML-alapú megosztását',
|
||||
'shareRss' => 'Engedélyezze a HTML & RSS megosztást',
|
||||
'state' => 'Státusz',
|
||||
'tags' => 'Rendezés címke szerint',
|
||||
'type' => 'Típus',
|
||||
),
|
||||
'get_all' => 'Minden cikk megjelenítése',
|
||||
'get_all_labels' => 'Display articles with any label', // TODO
|
||||
'get_all_labels' => 'Cikkek megjelenítése bármilyen címkével',
|
||||
'get_category' => 'Listáz “%s” kategóriát',
|
||||
'get_favorite' => 'Kedvenc cikkek megjelenítése',
|
||||
'get_feed' => 'Listáz “%s” hírforrást',
|
||||
'get_important' => 'Display articles from important feeds', // TODO
|
||||
'get_label' => 'Display articles with “%s” label', // TODO
|
||||
'help' => 'See the <a href="https://freshrss.github.io/FreshRSS/en/users/user_queries.html" target="_blank">documentation for user queries and resharing by HTML / RSS / OPML</a>.', // TODO
|
||||
'get_important' => 'Cikkek megjelenítése fontos hírforrásokból',
|
||||
'get_label' => ' “%s” címkével rendelkező cikkek megjelenítése',
|
||||
'help' => 'Lásd a <a href="https://freshrss.github.io/FreshRSS/en/users/user_queries.html" target="_blank"> dokumentációt a felhasználói lekérdezések és HTML/RSS/OPML megosztás témákban</a>.',
|
||||
'name' => 'Név',
|
||||
'no_filter' => 'Nincs szűrés',
|
||||
'number' => 'Lekérdezés %d',
|
||||
|
@ -141,11 +141,11 @@ return array(
|
|||
'order_desc' => 'Újabb cikkek előre',
|
||||
'search' => 'Keresse a “%s”',
|
||||
'share' => array(
|
||||
'_' => 'Share this query by link', // TODO
|
||||
'help' => 'Give this link if you want to share this query with anyone', // TODO
|
||||
'html' => 'Shareable link to the HTML page', // TODO
|
||||
'opml' => 'Shareable link to the OPML list of feeds', // TODO
|
||||
'rss' => 'Shareable link to the RSS feed', // TODO
|
||||
'_' => 'Lekérdezés megosztása linkkel',
|
||||
'help' => 'Ezt a linket küldd el hogy megoszd a lekérdezést',
|
||||
'html' => 'Megosztható link a HTML oldalhoz',
|
||||
'opml' => 'Megosztható link az OPML hírforrás listához',
|
||||
'rss' => 'Megosztható link az RSS hírforráshoz',
|
||||
),
|
||||
'state_0' => 'Minden cikk',
|
||||
'state_1' => 'Olvasott cikkek',
|
||||
|
|
|
@ -116,10 +116,10 @@ return array(
|
|||
),
|
||||
'tag' => array(
|
||||
'created' => 'Címke “%s” létrehozva.',
|
||||
'error' => 'Label could not be updated!', // TODO
|
||||
'error' => 'Nem sikerült a címke frissítése!',
|
||||
'name_exists' => 'Címke név már létezik.',
|
||||
'renamed' => 'Címke “%s” átnevezve “%s”.',
|
||||
'updated' => 'Label has been updated.', // TODO
|
||||
'updated' => 'Címke frissítése megtörtént.',
|
||||
),
|
||||
'update' => array(
|
||||
'can_apply' => 'Egy FreshRSS frissítés elérhető : <strong>Verzió %s</strong>.',
|
||||
|
|
|
@ -67,9 +67,9 @@ return array(
|
|||
'empty' => 'Ez a hírforrás üres. Ellenőrizd hogy van e tartalom rajta.',
|
||||
'error' => 'Ez a hírforrás nem működik. Ellenőrizd az elérhetőségét és frissítsd.',
|
||||
'export-as-opml' => array(
|
||||
'download' => 'Download', // TODO
|
||||
'help' => 'XML file', // TODO
|
||||
'label' => 'Export as OPML', // TODO
|
||||
'download' => 'Letöltés',
|
||||
'help' => 'XML fájl',
|
||||
'label' => 'Exportálás OPML formátumban',
|
||||
),
|
||||
'filteractions' => array(
|
||||
'_' => 'Szűrő műveletek',
|
||||
|
@ -113,7 +113,7 @@ return array(
|
|||
),
|
||||
'item_title' => array(
|
||||
'_' => 'elem cím',
|
||||
'help' => 'Használja az <a href="https://developer.mozilla.org/docs/Web/XPath/Axes" target="_blank">XPath axis</a> <code>descendant::</code> mint <code>descendant::h2</code>',
|
||||
'help' => 'Használja az <a href="https://developer.mozilla.org/docs/Web/XPath/Axes" target="_blank">XPath Axes</a> <code>descendant::</code> mint <code>descendant::h2</code>',
|
||||
),
|
||||
'item_uid' => array(
|
||||
'_' => 'elem egyedi ID',
|
||||
|
@ -127,44 +127,44 @@ return array(
|
|||
'xpath' => 'XPath ehhez:',
|
||||
),
|
||||
'json_dotnotation' => array(
|
||||
'_' => 'JSON (dot notation)', // TODO
|
||||
'_' => 'JSON (pont jelölés)',
|
||||
'feed_title' => array(
|
||||
'_' => 'feed title', // TODO
|
||||
'help' => 'Example: <code>meta.title</code> or a static string: <code>"My custom feed"</code>', // TODO
|
||||
'_' => 'hírforrás címe',
|
||||
'help' => 'Például: <code>meta.title</code> vagy egy statikus sztring: <code>"Az egyedi hírforrásom"</code>',
|
||||
),
|
||||
'help' => 'A JSON dot notated uses dots between objects and brackets for arrays (e.g. <code>data.items[0].title</code>)', // TODO
|
||||
'help' => 'A JSON pontjelölés pontokat használ az objektumok között és zárójeleket a tömbökhöz (pl. <code>data.items[0].title</code>)',
|
||||
'item' => array(
|
||||
'_' => 'finding news <strong>items</strong><br /><small>(most important)</small>', // TODO
|
||||
'help' => 'JSON path to the array containing the items, e.g. <code>newsItems</code>', // TODO
|
||||
'_' => 'hírek keresése <strong>elemek</strong><br /><small>(legfontosabb)</small>',
|
||||
'help' => 'JSON útvonal az elemeket tartalmazó tömbhöz, pl. <code>newsItems</code>',
|
||||
),
|
||||
'item_author' => 'item author', // TODO
|
||||
'item_categories' => 'item tags', // TODO
|
||||
'item_author' => 'elem szerző',
|
||||
'item_categories' => 'elem címkék',
|
||||
'item_content' => array(
|
||||
'_' => 'item content', // TODO
|
||||
'help' => 'Key under which the content is found, e.g. <code>content</code>', // TODO
|
||||
'_' => 'elem tartalom',
|
||||
'help' => 'Kulcs ami alatt a tartalom megtalálható, például <code>tartalom</code>',
|
||||
),
|
||||
'item_thumbnail' => array(
|
||||
'_' => 'item thumbnail', // TODO
|
||||
'help' => 'Example: <code>image</code>', // TODO
|
||||
'_' => 'elem előnézeti kép',
|
||||
'help' => 'Például: <code>image</code>',
|
||||
),
|
||||
'item_timeFormat' => array(
|
||||
'_' => 'Custom date/time format', // TODO
|
||||
'help' => 'Optional. A format supported by <a href="https://php.net/datetime.createfromformat" target="_blank"><code>DateTime::createFromFormat()</code></a> such as <code>d-m-Y H:i:s</code>', // TODO
|
||||
'_' => 'Egyedi dátum/idő formátum',
|
||||
'help' => 'Opcionális. Egy a <a href="https://php.net/datetime.createfromformat" target="_blank"><code>DateTime::createFromFormat()</code></a> funkció által támogatott formátum, például: <code>d-m-Y H:i:s</code>',
|
||||
),
|
||||
'item_timestamp' => array(
|
||||
'_' => 'item date', // TODO
|
||||
'help' => 'The result will be parsed by <a href="https://php.net/strtotime" target="_blank"><code>strtotime()</code></a>', // TODO
|
||||
'_' => 'elem dátum',
|
||||
'help' => 'Az eredményt az <a href="https://php.net/strtotime" target="_blank"><code>strtotime()</code></a> php függvény fogja értelmezni',
|
||||
),
|
||||
'item_title' => 'item title', // TODO
|
||||
'item_uid' => 'item unique ID', // TODO
|
||||
'item_title' => 'elem címe',
|
||||
'item_uid' => 'elem egyedi azonosító ID',
|
||||
'item_uri' => array(
|
||||
'_' => 'item link (URL)', // TODO
|
||||
'help' => 'Example: <code>permalink</code>', // TODO
|
||||
'_' => 'elem link (URL)',
|
||||
'help' => 'Például: <code>permalink</code>',
|
||||
),
|
||||
'json' => 'dot notation for:', // TODO
|
||||
'relative' => 'dot notated path (relative to item) for:', // TODO
|
||||
'json' => 'pontjelölés ehhez:',
|
||||
'relative' => 'pont jelölt útvonal (relatív az elemhez):',
|
||||
),
|
||||
'jsonfeed' => 'JSON Feed', // TODO
|
||||
'jsonfeed' => 'JSON Hírforrás',
|
||||
'rss' => 'RSS / Atom (alapértelmezett)',
|
||||
'xml_xpath' => 'XML + XPath', // IGNORE
|
||||
),
|
||||
|
@ -178,10 +178,10 @@ return array(
|
|||
'max_http_redir' => 'Max HTTP átirányítás',
|
||||
'max_http_redir_help' => '0 vagy üresen hagyva kikapcsolt, -1 a végtelen átirányításhoz',
|
||||
'method' => array(
|
||||
'_' => 'HTTP Method', // TODO
|
||||
'_' => 'HTTP Módszer',
|
||||
),
|
||||
'method_help' => 'The POST payload has automatic support for <code>application/x-www-form-urlencoded</code> and <code>application/json</code>', // TODO
|
||||
'method_postparams' => 'Payload for POST', // TODO
|
||||
'method_help' => 'A POST metódus hasznos adattartalma automatikusan támogatja az <code>application/x-www-form-urlencoded</code> és <code>application/json</code>',
|
||||
'method_postparams' => 'POST metódus adattartalma',
|
||||
'moved_category_deleted' => 'Ha kitörölsz egy kategóriát, az alá tartozó hírforrások automatikusan ide kerülnek <em>%s</em>.',
|
||||
'mute' => 'némítás',
|
||||
'no_selected' => 'Nincsen hírforrás kiválasztva.',
|
||||
|
@ -245,7 +245,7 @@ return array(
|
|||
'subscription_tools' => 'Hírforrás eszközök',
|
||||
),
|
||||
'tag' => array(
|
||||
'auto_label' => 'Add this label to new articles', // TODO
|
||||
'auto_label' => 'Adja hozzá automatikusan ezt a címkét az új cikkekhez',
|
||||
'name' => 'Név',
|
||||
'new_name' => 'Új név',
|
||||
'old_name' => 'Régi név',
|
||||
|
|
|
@ -18,25 +18,25 @@
|
|||
<div class="item search">
|
||||
<?php if (FreshRSS_Auth::hasAccess() || FreshRSS_Context::systemConf()->allow_anonymous) { ?>
|
||||
<form action="<?= $this->html_url ?>" method="get">
|
||||
<div class="stick">
|
||||
<?php if (Minz_Request::controllerName() === 'index'): ?>
|
||||
<?php if (in_array(Minz_Request::actionName(), ['normal', 'global', 'reader'], true)) { ?>
|
||||
<input type="hidden" name="a" value="<?= Minz_Request::actionName() ?>" />
|
||||
<?php } if (Minz_Request::paramString('get') !== '') { ?>
|
||||
<input type="hidden" name="get" value="<?= FreshRSS_Context::currentGet() ?>" />
|
||||
<?php } if (Minz_Request::paramInt('state') !== 0) { ?>
|
||||
<input type="hidden" name="state" value="<?= Minz_Request::paramInt('state') ?>" />
|
||||
<?php } ?>
|
||||
<?php endif; ?>
|
||||
<?php if (Minz_Request::paramString('user') !== '') { ?>
|
||||
<input type="hidden" name="user" value="<?= Minz_User::name() ?>" />
|
||||
<?php } if (ctype_alnum(Minz_Request::paramString('t'))) { ?>
|
||||
<input type="hidden" name="t" value="<?= Minz_Request::paramString('t') ?>" />
|
||||
<?php } if (ctype_upper(Minz_Request::paramString('order'))) { ?>
|
||||
<input type="hidden" name="order" value="<?= FreshRSS_Context::$order ?>" />
|
||||
<?php } if (ctype_lower(Minz_Request::paramString('f'))) { ?>
|
||||
<input type="hidden" name="f" value="<?= Minz_Request::paramString('f') ?>" />
|
||||
<?php if (Minz_Request::controllerName() === 'index'): ?>
|
||||
<?php if (in_array(Minz_Request::actionName(), ['normal', 'global', 'reader'], true)) { ?>
|
||||
<input type="hidden" name="a" value="<?= Minz_Request::actionName() ?>" />
|
||||
<?php } if (Minz_Request::paramString('get') !== '') { ?>
|
||||
<input type="hidden" name="get" value="<?= FreshRSS_Context::currentGet() ?>" />
|
||||
<?php } if (Minz_Request::paramInt('state') !== 0) { ?>
|
||||
<input type="hidden" name="state" value="<?= Minz_Request::paramInt('state') ?>" />
|
||||
<?php } ?>
|
||||
<?php endif; ?>
|
||||
<?php if (Minz_Request::paramString('user') !== '') { ?>
|
||||
<input type="hidden" name="user" value="<?= Minz_User::name() ?>" />
|
||||
<?php } if (ctype_alnum(Minz_Request::paramString('t'))) { ?>
|
||||
<input type="hidden" name="t" value="<?= Minz_Request::paramString('t') ?>" />
|
||||
<?php } if (ctype_upper(Minz_Request::paramString('order'))) { ?>
|
||||
<input type="hidden" name="order" value="<?= FreshRSS_Context::$order ?>" />
|
||||
<?php } if (ctype_lower(Minz_Request::paramString('f'))) { ?>
|
||||
<input type="hidden" name="f" value="<?= Minz_Request::paramString('f') ?>" />
|
||||
<?php } ?>
|
||||
<div class="stick">
|
||||
<input type="search" name="search" id="search"
|
||||
value="<?= htmlspecialchars(htmlspecialchars_decode(Minz_Request::paramString('search'), ENT_QUOTES), ENT_COMPAT, 'UTF-8') ?>"
|
||||
placeholder="<?= _t('gen.menu.search') ?>" />
|
||||
|
|
|
@ -27,8 +27,8 @@ function feedsToOutlines(array $feeds, bool $excludeMutedFeeds = false): array {
|
|||
case FreshRSS_Feed::KIND_XML_XPATH:
|
||||
$outline['type'] = FreshRSS_Export_Service::TYPE_XML_XPATH;
|
||||
break;
|
||||
case FreshRSS_Feed::KIND_JSON_DOTPATH:
|
||||
$outline['type'] = FreshRSS_Export_Service::TYPE_JSON_DOTPATH;
|
||||
case FreshRSS_Feed::KIND_JSON_DOTNOTATION:
|
||||
$outline['type'] = FreshRSS_Export_Service::TYPE_JSON_DOTNOTATION;
|
||||
break;
|
||||
case FreshRSS_Feed::KIND_JSONFEED:
|
||||
$outline['type'] = FreshRSS_Export_Service::TYPE_JSONFEED;
|
||||
|
@ -48,9 +48,9 @@ function feedsToOutlines(array $feeds, bool $excludeMutedFeeds = false): array {
|
|||
$outline['frss:xPathItemThumbnail'] = $xPathSettings['itemThumbnail'] ?? null;
|
||||
$outline['frss:xPathItemCategories'] = $xPathSettings['itemCategories'] ?? null;
|
||||
$outline['frss:xPathItemUid'] = $xPathSettings['itemUid'] ?? null;
|
||||
} elseif ($feed->kind() === FreshRSS_Feed::KIND_JSON_DOTPATH) {
|
||||
} elseif ($feed->kind() === FreshRSS_Feed::KIND_JSON_DOTNOTATION) {
|
||||
/** @var array<string,string> */
|
||||
$jsonSettings = $feed->attributeArray('json_dotpath') ?? [];
|
||||
$jsonSettings = $feed->attributeArray('json_dotnotation') ?? [];
|
||||
$outline['frss:jsonItem'] = $jsonSettings['item'] ?? null;
|
||||
$outline['frss:jsonItemTitle'] = $jsonSettings['itemTitle'] ?? null;
|
||||
$outline['frss:jsonItemContent'] = $jsonSettings['itemContent'] ?? null;
|
||||
|
|
|
@ -413,7 +413,7 @@
|
|||
<option value="<?= FreshRSS_Feed::KIND_HTML_XPATH ?>" <?= $this->feed->kind() === FreshRSS_Feed::KIND_HTML_XPATH ? 'selected="selected"' : '' ?> data-show="html_xpath"><?= _t('sub.feed.kind.html_xpath') ?></option>
|
||||
<option value="<?= FreshRSS_Feed::KIND_XML_XPATH ?>" <?= $this->feed->kind() === FreshRSS_Feed::KIND_XML_XPATH ? 'selected="selected"' : '' ?> data-show="html_xpath"><?= _t('sub.feed.kind.xml_xpath') ?></option>
|
||||
<option value="<?= FreshRSS_Feed::KIND_JSONFEED ?>" <?= $this->feed->kind() === FreshRSS_Feed::KIND_JSONFEED ? 'selected="selected"' : '' ?>><?= _t('sub.feed.kind.jsonfeed') ?></option>
|
||||
<option value="<?= FreshRSS_Feed::KIND_JSON_DOTPATH ?>" <?= $this->feed->kind() === FreshRSS_Feed::KIND_JSON_DOTPATH ? 'selected="selected"' : '' ?> data-show="json_dotpath"><?= _t('sub.feed.kind.json_dotnotation') ?></option>
|
||||
<option value="<?= FreshRSS_Feed::KIND_JSON_DOTNOTATION ?>" <?= $this->feed->kind() === FreshRSS_Feed::KIND_JSON_DOTNOTATION ? 'selected="selected"' : '' ?> data-show="json_dotnotation"><?= _t('sub.feed.kind.json_dotnotation') ?></option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -514,9 +514,9 @@
|
|||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset id="json_dotpath">
|
||||
<fieldset id="json_dotnotation">
|
||||
<?php
|
||||
$jsonSettings = Minz_Helper::htmlspecialchars_utf8($this->feed->attributeArray('json_dotpath') ?? []);
|
||||
$jsonSettings = Minz_Helper::htmlspecialchars_utf8($this->feed->attributeArray('json_dotnotation') ?? []);
|
||||
?>
|
||||
<p class="help"><?= _i('help') ?> <?= _t('sub.feed.kind.json_dotnotation.help') ?></p>
|
||||
<div class="form-group">
|
||||
|
|
|
@ -51,8 +51,8 @@
|
|||
endif;
|
||||
?></li><?php
|
||||
endif; ?>
|
||||
|
||||
<li class="item title<?= (($topline_thumbnail !== 'none') || $topline_summary) ? ' multiline' : '' ?>" dir="auto"><a target="_blank" rel="noreferrer" href="<?= $this->entry->link() ?>" class="item-element"><?= $this->entry->title() ?><?php
|
||||
<li class="item titleAuthorSummaryDate">
|
||||
<span class="item-element title<?= (($topline_thumbnail !== 'none') || $topline_summary) ? ' multiline' : '' ?>" dir="auto"><a target="_blank" rel="noreferrer" href="<?= $this->entry->link() ?>"><?= $this->entry->title() ?><?php
|
||||
if ($topline_display_authors):
|
||||
?><span class="author"><?php
|
||||
$authors = $this->entry->authors();
|
||||
|
@ -65,11 +65,13 @@
|
|||
}
|
||||
?></span><?php
|
||||
endif;
|
||||
if ($topline_summary):
|
||||
?><div class="summary"><?= trim(mb_substr(strip_tags($this->entry->content(false)), 0, 500, 'UTF-8')) ?></div><?php
|
||||
endif;
|
||||
?></a></li>
|
||||
<?php if ($topline_date) { ?><li class="item date"><time datetime="<?= $this->entry->machineReadableDate() ?>" class="item-element"><?= $this->entry->date() ?></time> </li><?php } ?>
|
||||
?></a></span>
|
||||
<?php
|
||||
if ($topline_summary):
|
||||
?><div class="summary"><?= trim(mb_substr(strip_tags($this->entry->content(false)), 0, 500, 'UTF-8')) ?></div><?php
|
||||
endif; ?>
|
||||
<?php if ($topline_date) { ?><span class="item-element date"><time datetime="<?= $this->entry->machineReadableDate() ?>"><?= $this->entry->date() ?></time> </span><?php } ?>
|
||||
</li>
|
||||
<?php if ($topline_link) { ?><li class="item link"><a target="_blank" rel="noreferrer" href="<?= $this->entry->link() ?>" class="item-element" title="<?=
|
||||
_t('conf.shortcut.see_on_website') ?>"><?= _i('link') ?></a></li><?php } ?>
|
||||
</ul>
|
||||
|
|
|
@ -72,7 +72,7 @@
|
|||
<option value="<?= FreshRSS_Feed::KIND_HTML_XPATH ?>" data-show="html_xpath"><?= _t('sub.feed.kind.html_xpath') ?></option>
|
||||
<option value="<?= FreshRSS_Feed::KIND_XML_XPATH ?>" data-show="html_xpath"><?= _t('sub.feed.kind.xml_xpath') ?></option>
|
||||
<option value="<?= FreshRSS_Feed::KIND_JSONFEED ?>"><?= _t('sub.feed.kind.jsonfeed') ?></option>
|
||||
<option value="<?= FreshRSS_Feed::KIND_JSON_DOTPATH ?>" data-show="json_dotpath"><?= _t('sub.feed.kind.json_dotnotation') ?></option>
|
||||
<option value="<?= FreshRSS_Feed::KIND_JSON_DOTNOTATION ?>" data-show="json_dotnotation"><?= _t('sub.feed.kind.json_dotnotation') ?></option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -166,7 +166,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset id="json_dotpath">
|
||||
<fieldset id="json_dotnotation">
|
||||
<p class="help"><?= _i('help') ?> <?= _t('sub.feed.kind.json_dotnotation.help') ?></p>
|
||||
<div class="form-group">
|
||||
<label class="group-name" for="jsonFeedTitle"><small><?= _t('sub.feed.kind.json_dotnotation.json') ?></small><br />
|
||||
|
|
|
@ -121,6 +121,14 @@ cd /usr/share/FreshRSS
|
|||
```sh
|
||||
cd /usr/share/FreshRSS
|
||||
|
||||
./cli/db-backup.php
|
||||
# Back-up all users respective database to `data/users/*/backup.sqlite`
|
||||
|
||||
./cli/db-restore.php --delete-backup --force-overwrite
|
||||
# Restore all users respective database from `data/users/*/backup.sqlite`
|
||||
# --delete-backup: delete `data/users/*/backup.sqlite` after successful import
|
||||
# --force-overwrite: will clear the users respective database before import
|
||||
|
||||
./cli/db-optimize.php --user username
|
||||
# Optimize database (reduces the size) for a given user (perform `OPTIMIZE TABLE` in MySQL, `VACUUM` in SQLite)
|
||||
```
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require(__DIR__ . '/_cli.php');
|
||||
|
||||
performRequirementCheck(FreshRSS_Context::systemConf()->db['type'] ?? '');
|
||||
$ok = true;
|
||||
|
||||
foreach (listUsers() as $username) {
|
||||
$username = cliInitUser($username);
|
||||
$filename = DATA_PATH . '/users/' . $username . '/backup.sqlite';
|
||||
@unlink($filename);
|
||||
|
||||
echo 'FreshRSS backup database to SQLite for user “', $username, "”…\n";
|
||||
|
||||
$databaseDAO = FreshRSS_Factory::createDatabaseDAO($username);
|
||||
$ok &= $databaseDAO->dbCopy($filename, FreshRSS_DatabaseDAO::SQLITE_EXPORT);
|
||||
}
|
||||
|
||||
done((bool)$ok);
|
|
@ -0,0 +1,65 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require(__DIR__ . '/_cli.php');
|
||||
|
||||
performRequirementCheck(FreshRSS_Context::systemConf()->db['type'] ?? '');
|
||||
|
||||
$cliOptions = new class extends CliOptionsParser {
|
||||
public string $deleteBackup;
|
||||
public string $forceOverwrite;
|
||||
|
||||
public function __construct() {
|
||||
$this->addOption('deleteBackup', (new CliOption('delete-backup'))->withValueNone());
|
||||
$this->addOption('forceOverwrite', (new CliOption('force-overwrite'))->withValueNone());
|
||||
parent::__construct();
|
||||
}
|
||||
};
|
||||
|
||||
if (!empty($cliOptions->errors)) {
|
||||
fail('FreshRSS error: ' . array_shift($cliOptions->errors) . "\n" . $cliOptions->usage);
|
||||
}
|
||||
|
||||
FreshRSS_Context::initSystem(true);
|
||||
Minz_User::change(Minz_User::INTERNAL_USER);
|
||||
$ok = false;
|
||||
try {
|
||||
$error = initDb();
|
||||
if ($error != '') {
|
||||
$_SESSION['bd_error'] = $error;
|
||||
} else {
|
||||
$ok = true;
|
||||
}
|
||||
} catch (Exception $ex) {
|
||||
$_SESSION['bd_error'] = $ex->getMessage();
|
||||
}
|
||||
if (!$ok) {
|
||||
fail('FreshRSS database error: ' . (empty($_SESSION['bd_error']) ? 'Unknown error' : $_SESSION['bd_error']));
|
||||
}
|
||||
|
||||
foreach (listUsers() as $username) {
|
||||
$username = cliInitUser($username);
|
||||
$filename = DATA_PATH . "/users/{$username}/backup.sqlite";
|
||||
if (!file_exists($filename)) {
|
||||
fwrite(STDERR, "FreshRSS SQLite backup not found for user “{$username}”!\n");
|
||||
$ok = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
echo 'FreshRSS restore database from SQLite for user “', $username, "”…\n";
|
||||
|
||||
$databaseDAO = FreshRSS_Factory::createDatabaseDAO($username);
|
||||
$clearFirst = isset($cliOptions->forceOverwrite);
|
||||
$ok &= $databaseDAO->dbCopy($filename, FreshRSS_DatabaseDAO::SQLITE_IMPORT, $clearFirst);
|
||||
if ($ok) {
|
||||
if (isset($cliOptions->deleteBackup)) {
|
||||
unlink($filename);
|
||||
}
|
||||
} else {
|
||||
fwrite(STDERR, "FreshRSS database already exists for user “{$username}”!\n");
|
||||
fwrite(STDERR, "If you would like to clear the user database first, use the option --force-overwrite\n");
|
||||
}
|
||||
invalidateHttpCache($username);
|
||||
}
|
||||
|
||||
done((bool)$ok);
|
|
@ -10,9 +10,19 @@ Do this before an upgrade.
|
|||
|
||||
This following tutorial demonstrates commands for backing up FreshRSS. It assumes that your main FreshRSS directory is `/usr/share/FreshRSS`. If you’ve installed it somewhere else, substitute your path as necessary.
|
||||
|
||||
### Creating a database backup
|
||||
|
||||
Back-up all users respective database to `data/users/*/backup.sqlite`
|
||||
|
||||
```sh
|
||||
cd /usr/share/FreshRSS/
|
||||
./cli/db-backup.php
|
||||
```
|
||||
|
||||
### Creating a Backup of all Files
|
||||
|
||||
First, Enter the directory you wish to save your backup to. Here, for example, we’ll save the backup to the user home directory
|
||||
Enter the directory you wish to save your backup to.
|
||||
Here, for example, we’ll save the backup to the user home directory
|
||||
|
||||
```sh
|
||||
cd ~
|
||||
|
@ -52,7 +62,39 @@ And optionally, as cleanup, remove the copy of your backup from the FreshRSS dir
|
|||
rm FreshRSS-backup.tgz
|
||||
```
|
||||
|
||||
## Backing up Feeds
|
||||
### Restore a database backup
|
||||
|
||||
> ℹ️ It is safer to stop your Web server and cron during maintenance operations.
|
||||
|
||||
Restore all users respective database from `data/users/*/backup.sqlite`
|
||||
|
||||
```sh
|
||||
cd /usr/share/FreshRSS/
|
||||
./cli/db-restore.php --delete-backup --force-overwrite
|
||||
```
|
||||
|
||||
## Migrate database
|
||||
|
||||
Start by making an automatic backup of the all the user databases to SQLite files:
|
||||
|
||||
```sh
|
||||
cd /usr/share/FreshRSS/
|
||||
./cli/db-backup.php
|
||||
```
|
||||
|
||||
Change your database setup:
|
||||
- if you like to change database type (e.g. from MySQL to PostgreSQL), edit `data/config.php` accordingly.
|
||||
- if you upgrade to a major PostgreSQL version, after a PostgreSQL backup, you may delete the old instance and start a new instance (remove the PostgreSQL volume if using Docker).
|
||||
|
||||
Restore all the user databases from the SQLite files:
|
||||
|
||||
```sh
|
||||
./cli/db-restore.php --delete-backup --force-overwrite
|
||||
```
|
||||
|
||||
See also our [Docker documentation to migrate database](https://github.com/FreshRSS/FreshRSS/blob/edge/Docker/README.md#migrate-database).
|
||||
|
||||
## Backing up selected content
|
||||
|
||||
### Feed list Export
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@ To set up FreshRSS behind a reverse proxy with Caddy and using a subfolder, foll
|
|||
Restart FreshRSS to ensure that it recognizes the new base URL:
|
||||
|
||||
```bash
|
||||
docker-compose restart freshrss
|
||||
docker compose restart freshrss
|
||||
```
|
||||
|
||||
4. **Access FreshRSS:**
|
||||
|
|
|
@ -44,28 +44,28 @@ The following attributes are using similar naming conventions than [RSS-Bridge](
|
|||
* `frss:xPathItemCategories`: XPath expression for extracting a list of categories (tags) from the item context.
|
||||
* `frss:xPathItemUid`: XPath expression for extracting an item’s unique ID from the item context. If left empty, a hash is computed automatically.
|
||||
|
||||
### JSON+DotPath
|
||||
### JSON+DotNotation
|
||||
|
||||
* `<outline type="JSON+DotPath" ...`: Similar to `HTML+XPath` but for JSON and using a dot/bracket syntax such as `object.object.array[2].property`.
|
||||
* `<outline type="JSON+DotNotation" ...`: Similar to `HTML+XPath` but for JSON and using a dot/bracket syntax such as `object.object.array[2].property`.
|
||||
|
||||
* `frss:jsonItem`: JSON dot path for extracting the feed items from the source page.
|
||||
* `frss:jsonItem`: JSON dot notation for extracting the feed items from the source page.
|
||||
* Example: `data.items`
|
||||
* `frss:jsonItemTitle`: JSON dot path for extracting the item’s title from the item context.
|
||||
* `frss:jsonItemTitle`: JSON dot notation for extracting the item’s title from the item context.
|
||||
* Example: `meta.title`
|
||||
* `frss:jsonItemContent`: JSON dot path for extracting an item’s content from the item context.
|
||||
* `frss:jsonItemContent`: JSON dot notation for extracting an item’s content from the item context.
|
||||
* Example: `content`
|
||||
* `frss:jsonItemUri`: JSON dot path for extracting an item link from the item context.
|
||||
* `frss:jsonItemUri`: JSON dot notation for extracting an item link from the item context.
|
||||
* Example: `meta.links[0]`
|
||||
* `frss:jsonItemAuthor`: JSON dot path for extracting an item author from the item context.
|
||||
* `frss:jsonItemTimestamp`: JSON dot path for extracting an item timestamp from the item context. The result will be parsed by [`strtotime()`](https://php.net/strtotime).
|
||||
* `frss:jsonItemAuthor`: JSON dot notation for extracting an item author from the item context.
|
||||
* `frss:jsonItemTimestamp`: JSON dot notation for extracting an item timestamp from the item context. The result will be parsed by [`strtotime()`](https://php.net/strtotime).
|
||||
* `frss:jsonItemTimeFormat`: Date/Time format to parse the timestamp, according to [`DateTime::createFromFormat()`](https://php.net/datetime.createfromformat).
|
||||
* `frss:jsonItemThumbnail`: JSON dot path for extracting an item’s thumbnail (image) URL from the item context.
|
||||
* `frss:jsonItemCategories`: JSON dot path for extracting a list of categories (tags) from the item context.
|
||||
* `frss:jsonItemUid`: JSON dot path for extracting an item’s unique ID from the item context. If left empty, a hash is computed automatically.
|
||||
* `frss:jsonItemThumbnail`: JSON dot notation for extracting an item’s thumbnail (image) URL from the item context.
|
||||
* `frss:jsonItemCategories`: JSON dot notation for extracting a list of categories (tags) from the item context.
|
||||
* `frss:jsonItemUid`: JSON dot notation for extracting an item’s unique ID from the item context. If left empty, a hash is computed automatically.
|
||||
|
||||
### JSON Feed
|
||||
|
||||
* `<outline type="JSONFeed" ...`: Uses `JSON+DotPath` behind the scenes to parse a [JSON Feed](https://www.jsonfeed.org/).
|
||||
* `<outline type="JSONFeed" ...`: Uses `JSON+DotNotation` behind the scenes to parse a [JSON Feed](https://www.jsonfeed.org/).
|
||||
|
||||
### cURL
|
||||
|
||||
|
|
|
@ -1081,7 +1081,7 @@ function init_stream(stream) {
|
|||
return true;
|
||||
}
|
||||
|
||||
el = ev.target.closest('.item.title > a');
|
||||
el = ev.target.closest('.item .title > a');
|
||||
if (el) { // Allow default control/command-click behaviour such as open in background-tab
|
||||
return ev.ctrlKey || ev.metaKey;
|
||||
}
|
||||
|
@ -1189,7 +1189,7 @@ function init_stream(stream) {
|
|||
return;
|
||||
}
|
||||
|
||||
let el = ev.target.closest('.item.title > a');
|
||||
let el = ev.target.closest('.item .title > a');
|
||||
if (el) {
|
||||
if (ev.which == 1) {
|
||||
if (ev.ctrlKey) { // Control+click
|
||||
|
|
|
@ -747,11 +747,11 @@ kbd {
|
|||
background: var(--background-color-dark);
|
||||
}
|
||||
|
||||
.flux.not_read:not(.current):hover .item.title {
|
||||
background: inherit;
|
||||
.flux.not_read:not(.current):hover .item .title {
|
||||
background: var(--background-color-hover);
|
||||
}
|
||||
|
||||
.flux:not(.current):hover .item.title {
|
||||
.flux:not(.current):hover .item .title {
|
||||
background: var(--background-color-hover);
|
||||
}
|
||||
|
||||
|
@ -759,11 +759,11 @@ kbd {
|
|||
background: var(--background-color-hover);
|
||||
}
|
||||
|
||||
.flux .flux_header .item.title a {
|
||||
.flux .flux_header .item .title a {
|
||||
color: var(--font-color-light);
|
||||
}
|
||||
|
||||
.flux .flux_header .item.title .author {
|
||||
.flux .flux_header .item .title .author {
|
||||
color: var(--font-color-dark);
|
||||
}
|
||||
|
||||
|
@ -794,9 +794,9 @@ kbd {
|
|||
filter: grayscale(0.8) brightness(1.7);
|
||||
}
|
||||
|
||||
.flux .item.date {
|
||||
.flux .flux_header .date,
|
||||
.flux .flux_content .bottom .date {
|
||||
color: var(--font-color-light);
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.flux .bottom {
|
||||
|
|
|
@ -747,11 +747,11 @@ kbd {
|
|||
background: var(--background-color-dark);
|
||||
}
|
||||
|
||||
.flux.not_read:not(.current):hover .item.title {
|
||||
background: inherit;
|
||||
.flux.not_read:not(.current):hover .item .title {
|
||||
background: var(--background-color-hover);
|
||||
}
|
||||
|
||||
.flux:not(.current):hover .item.title {
|
||||
.flux:not(.current):hover .item .title {
|
||||
background: var(--background-color-hover);
|
||||
}
|
||||
|
||||
|
@ -759,11 +759,11 @@ kbd {
|
|||
background: var(--background-color-hover);
|
||||
}
|
||||
|
||||
.flux .flux_header .item.title a {
|
||||
.flux .flux_header .item .title a {
|
||||
color: var(--font-color-light);
|
||||
}
|
||||
|
||||
.flux .flux_header .item.title .author {
|
||||
.flux .flux_header .item .title .author {
|
||||
color: var(--font-color-dark);
|
||||
}
|
||||
|
||||
|
@ -794,9 +794,9 @@ kbd {
|
|||
filter: grayscale(0.8) brightness(1.7);
|
||||
}
|
||||
|
||||
.flux .item.date {
|
||||
.flux .flux_header .date,
|
||||
.flux .flux_content .bottom .date {
|
||||
color: var(--font-color-light);
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.flux .bottom {
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
&:hover {
|
||||
background: variables.$grey-lighter;
|
||||
|
||||
&:not(.current):hover .item.title {
|
||||
&:not(.current):hover .item .title {
|
||||
background: variables.$grey-lighter;
|
||||
}
|
||||
}
|
||||
|
@ -33,13 +33,13 @@
|
|||
&.not_read:not(.current) {
|
||||
background: variables.$unread-bg;
|
||||
|
||||
&:hover .item.title {
|
||||
&:hover .item .title {
|
||||
background: variables.$unread-bg;
|
||||
}
|
||||
}
|
||||
|
||||
&.not_read {
|
||||
.item.title {
|
||||
.item .title {
|
||||
a {
|
||||
color: variables.$unread-font-color;
|
||||
}
|
||||
|
@ -51,7 +51,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.item.date {
|
||||
.item .date {
|
||||
color: color.scale(variables.$unread-font-color, $alpha: -50%);
|
||||
}
|
||||
}
|
||||
|
@ -65,7 +65,7 @@
|
|||
&.favorite:not(.current) {
|
||||
background: variables.$fav-light;
|
||||
|
||||
&:hover .item.title {
|
||||
&:hover .item .title {
|
||||
background: variables.$fav-light;
|
||||
}
|
||||
}
|
||||
|
@ -77,7 +77,8 @@
|
|||
}
|
||||
}
|
||||
|
||||
.item.date {
|
||||
.flux_header .date,
|
||||
.flux_content .bottom .date {
|
||||
color: color.scale(variables.$main-font-color, $alpha: -50%);
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
|
|
@ -989,7 +989,7 @@ main.prompt {
|
|||
.flux .flux_header:hover {
|
||||
background: #fcfaf8;
|
||||
}
|
||||
.flux .flux_header:hover:not(.current):hover .item.title {
|
||||
.flux .flux_header:hover:not(.current):hover .item .title {
|
||||
background: #fcfaf8;
|
||||
}
|
||||
.flux.current {
|
||||
|
@ -1002,16 +1002,16 @@ main.prompt {
|
|||
.flux.not_read:not(.current) {
|
||||
background: #f2f6f8;
|
||||
}
|
||||
.flux.not_read:not(.current):hover .item.title {
|
||||
.flux.not_read:not(.current):hover .item .title {
|
||||
background: #f2f6f8;
|
||||
}
|
||||
.flux.not_read .item.title a {
|
||||
.flux.not_read .item .title a {
|
||||
color: #161a38;
|
||||
}
|
||||
.flux.not_read .item.website a {
|
||||
color: #161a38;
|
||||
}
|
||||
.flux.not_read .item.date {
|
||||
.flux.not_read .item .date {
|
||||
color: rgba(22, 26, 56, 0.5);
|
||||
}
|
||||
.flux.favorite {
|
||||
|
@ -1021,14 +1021,15 @@ main.prompt {
|
|||
.flux.favorite:not(.current) {
|
||||
background: #fff6da;
|
||||
}
|
||||
.flux.favorite:not(.current):hover .item.title {
|
||||
.flux.favorite:not(.current):hover .item .title {
|
||||
background: #fff6da;
|
||||
}
|
||||
.flux .website a {
|
||||
color: #363330;
|
||||
opacity: 0.75;
|
||||
}
|
||||
.flux .item.date {
|
||||
.flux .flux_header .date,
|
||||
.flux .flux_content .bottom .date {
|
||||
color: rgba(54, 51, 48, 0.5);
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
|
|
@ -989,7 +989,7 @@ main.prompt {
|
|||
.flux .flux_header:hover {
|
||||
background: #fcfaf8;
|
||||
}
|
||||
.flux .flux_header:hover:not(.current):hover .item.title {
|
||||
.flux .flux_header:hover:not(.current):hover .item .title {
|
||||
background: #fcfaf8;
|
||||
}
|
||||
.flux.current {
|
||||
|
@ -1002,16 +1002,16 @@ main.prompt {
|
|||
.flux.not_read:not(.current) {
|
||||
background: #f2f6f8;
|
||||
}
|
||||
.flux.not_read:not(.current):hover .item.title {
|
||||
.flux.not_read:not(.current):hover .item .title {
|
||||
background: #f2f6f8;
|
||||
}
|
||||
.flux.not_read .item.title a {
|
||||
.flux.not_read .item .title a {
|
||||
color: #161a38;
|
||||
}
|
||||
.flux.not_read .item.website a {
|
||||
color: #161a38;
|
||||
}
|
||||
.flux.not_read .item.date {
|
||||
.flux.not_read .item .date {
|
||||
color: rgba(22, 26, 56, 0.5);
|
||||
}
|
||||
.flux.favorite {
|
||||
|
@ -1021,14 +1021,15 @@ main.prompt {
|
|||
.flux.favorite:not(.current) {
|
||||
background: #fff6da;
|
||||
}
|
||||
.flux.favorite:not(.current):hover .item.title {
|
||||
.flux.favorite:not(.current):hover .item .title {
|
||||
background: #fff6da;
|
||||
}
|
||||
.flux .website a {
|
||||
color: #363330;
|
||||
opacity: 0.75;
|
||||
}
|
||||
.flux .item.date {
|
||||
.flux .flux_header .date,
|
||||
.flux .flux_content .bottom .date {
|
||||
color: rgba(54, 51, 48, 0.5);
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
|
|
@ -452,15 +452,16 @@ button.as-link[disabled] {
|
|||
background-color: var(--dark-background-color1);
|
||||
}
|
||||
|
||||
.flux .flux_header .item.title a {
|
||||
.flux .flux_header .item .title a {
|
||||
color: var(--dark-font-color8);
|
||||
}
|
||||
|
||||
.flux .item.date {
|
||||
.flux .flux_header .date,
|
||||
.flux .flux_content .bottom .date {
|
||||
color: var(--dark-font-color6);
|
||||
}
|
||||
|
||||
.flux:not(.current):hover .item.title {
|
||||
.flux:not(.current):hover .item .title {
|
||||
background-color: var(--dark-background-color1);
|
||||
}
|
||||
|
||||
|
|
|
@ -452,15 +452,16 @@ button.as-link[disabled] {
|
|||
background-color: var(--dark-background-color1);
|
||||
}
|
||||
|
||||
.flux .flux_header .item.title a {
|
||||
.flux .flux_header .item .title a {
|
||||
color: var(--dark-font-color8);
|
||||
}
|
||||
|
||||
.flux .item.date {
|
||||
.flux .flux_header .date,
|
||||
.flux .flux_content .bottom .date {
|
||||
color: var(--dark-font-color6);
|
||||
}
|
||||
|
||||
.flux:not(.current):hover .item.title {
|
||||
.flux:not(.current):hover .item .title {
|
||||
background-color: var(--dark-background-color1);
|
||||
}
|
||||
|
||||
|
|
|
@ -693,6 +693,7 @@ th {
|
|||
border-left: 2px solid #ecf0f1;
|
||||
}
|
||||
|
||||
.flux:not(.current):hover .flux_header .title,
|
||||
.flux .flux_header:hover {
|
||||
background: #fff;
|
||||
}
|
||||
|
@ -728,9 +729,9 @@ th {
|
|||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.flux .item.date {
|
||||
.flux .flux_header .date,
|
||||
.flux .flux_content .bottom .date {
|
||||
color: #666;
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.flux .bottom {
|
||||
|
|
|
@ -693,6 +693,7 @@ th {
|
|||
border-right: 2px solid #ecf0f1;
|
||||
}
|
||||
|
||||
.flux:not(.current):hover .flux_header .title,
|
||||
.flux .flux_header:hover {
|
||||
background: #fff;
|
||||
}
|
||||
|
@ -728,9 +729,9 @@ th {
|
|||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.flux .item.date {
|
||||
.flux .flux_header .date,
|
||||
.flux .flux_content .bottom .date {
|
||||
color: #666;
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.flux .bottom {
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
&:hover {
|
||||
background: variables.$grey-lighter;
|
||||
|
||||
&:not(.current):hover .item.title {
|
||||
&:not(.current):hover .item .title {
|
||||
background: variables.$grey-lighter;
|
||||
}
|
||||
}
|
||||
|
@ -28,13 +28,13 @@
|
|||
&.not_read:not(.current) {
|
||||
background: variables.$unread-bg;
|
||||
|
||||
&:hover .item.title {
|
||||
&:hover .item .title {
|
||||
background: variables.$unread-bg;
|
||||
}
|
||||
}
|
||||
|
||||
&.not_read {
|
||||
.item.title {
|
||||
.item .title {
|
||||
a {
|
||||
color: variables.$unread-font-color;
|
||||
}
|
||||
|
@ -47,7 +47,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.item.date {
|
||||
.item .date {
|
||||
color: color.scale(variables.$unread-font-color, $alpha: -50%);
|
||||
}
|
||||
}
|
||||
|
@ -61,7 +61,7 @@
|
|||
&.favorite:not(.current) {
|
||||
background: variables.$fav-light;
|
||||
|
||||
&:hover .item.title {
|
||||
&:hover .item .title {
|
||||
background: variables.$fav-light;
|
||||
}
|
||||
}
|
||||
|
@ -73,7 +73,8 @@
|
|||
}
|
||||
}
|
||||
|
||||
.item.date {
|
||||
.flux_header .date,
|
||||
.flux_content .bottom .date {
|
||||
color: color.scale(variables.$main-font-color, $alpha: -50%);
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
|
|
@ -1007,7 +1007,7 @@ main.prompt {
|
|||
.flux .flux_header:hover {
|
||||
background: #f9fafb;
|
||||
}
|
||||
.flux .flux_header:hover:not(.current):hover .item.title {
|
||||
.flux .flux_header:hover:not(.current):hover .item .title {
|
||||
background: #f9fafb;
|
||||
}
|
||||
.flux.current {
|
||||
|
@ -1017,16 +1017,16 @@ main.prompt {
|
|||
.flux.not_read:not(.current) {
|
||||
background: #f2f6f8;
|
||||
}
|
||||
.flux.not_read:not(.current):hover .item.title {
|
||||
.flux.not_read:not(.current):hover .item .title {
|
||||
background: #f2f6f8;
|
||||
}
|
||||
.flux.not_read .item.title a {
|
||||
.flux.not_read .item .title a {
|
||||
color: #36c;
|
||||
}
|
||||
.flux.not_read .item.website a {
|
||||
color: #36c;
|
||||
}
|
||||
.flux.not_read .item.date {
|
||||
.flux.not_read .item .date {
|
||||
color: rgba(51, 102, 204, 0.5);
|
||||
}
|
||||
.flux.favorite {
|
||||
|
@ -1036,14 +1036,15 @@ main.prompt {
|
|||
.flux.favorite:not(.current) {
|
||||
background: #fff6da;
|
||||
}
|
||||
.flux.favorite:not(.current):hover .item.title {
|
||||
.flux.favorite:not(.current):hover .item .title {
|
||||
background: #fff6da;
|
||||
}
|
||||
.flux .website a {
|
||||
color: #303136;
|
||||
opacity: 0.75;
|
||||
}
|
||||
.flux .item.date {
|
||||
.flux .flux_header .date,
|
||||
.flux .flux_content .bottom .date {
|
||||
color: rgba(48, 49, 54, 0.5);
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
|
|
@ -1007,7 +1007,7 @@ main.prompt {
|
|||
.flux .flux_header:hover {
|
||||
background: #f9fafb;
|
||||
}
|
||||
.flux .flux_header:hover:not(.current):hover .item.title {
|
||||
.flux .flux_header:hover:not(.current):hover .item .title {
|
||||
background: #f9fafb;
|
||||
}
|
||||
.flux.current {
|
||||
|
@ -1017,16 +1017,16 @@ main.prompt {
|
|||
.flux.not_read:not(.current) {
|
||||
background: #f2f6f8;
|
||||
}
|
||||
.flux.not_read:not(.current):hover .item.title {
|
||||
.flux.not_read:not(.current):hover .item .title {
|
||||
background: #f2f6f8;
|
||||
}
|
||||
.flux.not_read .item.title a {
|
||||
.flux.not_read .item .title a {
|
||||
color: #36c;
|
||||
}
|
||||
.flux.not_read .item.website a {
|
||||
color: #36c;
|
||||
}
|
||||
.flux.not_read .item.date {
|
||||
.flux.not_read .item .date {
|
||||
color: rgba(51, 102, 204, 0.5);
|
||||
}
|
||||
.flux.favorite {
|
||||
|
@ -1036,14 +1036,15 @@ main.prompt {
|
|||
.flux.favorite:not(.current) {
|
||||
background: #fff6da;
|
||||
}
|
||||
.flux.favorite:not(.current):hover .item.title {
|
||||
.flux.favorite:not(.current):hover .item .title {
|
||||
background: #fff6da;
|
||||
}
|
||||
.flux .website a {
|
||||
color: #303136;
|
||||
opacity: 0.75;
|
||||
}
|
||||
.flux .item.date {
|
||||
.flux .flux_header .date,
|
||||
.flux .flux_content .bottom .date {
|
||||
color: rgba(48, 49, 54, 0.5);
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
|
|
@ -791,10 +791,6 @@ li.item.active {
|
|||
padding: 0.25rem;
|
||||
}
|
||||
|
||||
.flux .item.date {
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.flux .bottom {
|
||||
font-size: 0.8rem;
|
||||
text-align: center;
|
||||
|
@ -809,23 +805,23 @@ li.item.active {
|
|||
background: var(--accent-bg);
|
||||
}
|
||||
|
||||
.flux:not(.current):hover .item.title {
|
||||
.flux:not(.current):hover .item .title {
|
||||
background: var(--accent-bg);
|
||||
transition: .3s;
|
||||
}
|
||||
|
||||
.flux .flux_header .item.title a {
|
||||
.flux .flux_header .item .title a {
|
||||
color: var(--text-default);
|
||||
}
|
||||
|
||||
.flux .flux_header .item.title .summary {
|
||||
.flux .flux_header .item .title .summary {
|
||||
color: var(--text-accent);
|
||||
font-size: 0.8rem;
|
||||
font-style: italic;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.flux .flux_header .item.title .author {
|
||||
.flux .flux_header .item .title .author {
|
||||
color: var(--text-accent);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
|
|
@ -791,10 +791,6 @@ li.item.active {
|
|||
padding: 0.25rem;
|
||||
}
|
||||
|
||||
.flux .item.date {
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.flux .bottom {
|
||||
font-size: 0.8rem;
|
||||
text-align: center;
|
||||
|
@ -809,23 +805,23 @@ li.item.active {
|
|||
background: var(--accent-bg);
|
||||
}
|
||||
|
||||
.flux:not(.current):hover .item.title {
|
||||
.flux:not(.current):hover .item .title {
|
||||
background: var(--accent-bg);
|
||||
transition: .3s;
|
||||
}
|
||||
|
||||
.flux .flux_header .item.title a {
|
||||
.flux .flux_header .item .title a {
|
||||
color: var(--text-default);
|
||||
}
|
||||
|
||||
.flux .flux_header .item.title .summary {
|
||||
.flux .flux_header .item .title .summary {
|
||||
color: var(--text-accent);
|
||||
font-size: 0.8rem;
|
||||
font-style: italic;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.flux .flux_header .item.title .author {
|
||||
.flux .flux_header .item .title .author {
|
||||
color: var(--text-accent);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
|
|
@ -837,6 +837,7 @@ a:hover .icon {
|
|||
}
|
||||
|
||||
.flux .flux_header:not(.current):hover .flux_header,
|
||||
.flux:not(.current):hover .flux_header .title,
|
||||
.flux.current .flux_header {
|
||||
background-color: var(--background-color-hover);
|
||||
}
|
||||
|
@ -849,6 +850,7 @@ a:hover .icon {
|
|||
background-color: var(--unread-article-background-color);
|
||||
}
|
||||
|
||||
.flux.not_read:not(.current):hover .flux_header .title,
|
||||
.flux.not_read:not(.current):hover .flux_header,
|
||||
.flux.not_read.current .flux_header {
|
||||
background-color: var(--unread-article-background-color-hover);
|
||||
|
@ -862,6 +864,7 @@ a:hover .icon {
|
|||
background-color: var(--favorite-article-background-color);
|
||||
}
|
||||
|
||||
.flux.favorite:not(.current):hover .flux_header .title,
|
||||
.flux.favorite:not(.current):hover .flux_header,
|
||||
.flux.favorite.current .flux_header {
|
||||
background-color: var(--favorite-article-background-color-hover);
|
||||
|
@ -873,7 +876,7 @@ a:hover .icon {
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
.flux .item.date {
|
||||
.flux .item .date {
|
||||
color: var(--font-color-grey);
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
|
|
@ -837,6 +837,7 @@ a:hover .icon {
|
|||
}
|
||||
|
||||
.flux .flux_header:not(.current):hover .flux_header,
|
||||
.flux:not(.current):hover .flux_header .title,
|
||||
.flux.current .flux_header {
|
||||
background-color: var(--background-color-hover);
|
||||
}
|
||||
|
@ -849,6 +850,7 @@ a:hover .icon {
|
|||
background-color: var(--unread-article-background-color);
|
||||
}
|
||||
|
||||
.flux.not_read:not(.current):hover .flux_header .title,
|
||||
.flux.not_read:not(.current):hover .flux_header,
|
||||
.flux.not_read.current .flux_header {
|
||||
background-color: var(--unread-article-background-color-hover);
|
||||
|
@ -862,6 +864,7 @@ a:hover .icon {
|
|||
background-color: var(--favorite-article-background-color);
|
||||
}
|
||||
|
||||
.flux.favorite:not(.current):hover .flux_header .title,
|
||||
.flux.favorite:not(.current):hover .flux_header,
|
||||
.flux.favorite.current .flux_header {
|
||||
background-color: var(--favorite-article-background-color-hover);
|
||||
|
@ -873,7 +876,7 @@ a:hover .icon {
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
.flux .item.date {
|
||||
.flux .item .date {
|
||||
color: var(--font-color-grey);
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
|
|
@ -798,6 +798,7 @@ a.signin {
|
|||
}
|
||||
|
||||
.flux .flux_header:hover,
|
||||
.flux:not(.current):hover .flux_header .title,
|
||||
.flux.current .flux_header {
|
||||
background-color: var(--background-color-grey-hover);
|
||||
}
|
||||
|
@ -815,8 +816,8 @@ a.signin {
|
|||
border-left: 2px solid var(--border-left-article-unread);
|
||||
}
|
||||
|
||||
.flux .flux_header .item.title a,
|
||||
.flux.not_read:not(.current):hover .flux_header .item.title {
|
||||
.flux .flux_header .item .title a,
|
||||
.flux.not_read:not(.current):hover .flux_header .item .title {
|
||||
color: var(--font-color-link-title);
|
||||
}
|
||||
|
||||
|
@ -828,6 +829,7 @@ a.signin {
|
|||
background-color: var(--background-color-favorite);
|
||||
}
|
||||
|
||||
.flux.favorite:not(.current):hover .flux_header .title,
|
||||
.flux.favorite:not(.current) .flux_header:hover,
|
||||
.flux.favorite.current .flux_header {
|
||||
background-color: var(--background-color-favorite-hover);
|
||||
|
@ -847,9 +849,9 @@ a.signin {
|
|||
padding: 0.425rem;
|
||||
}
|
||||
|
||||
.flux .item.date {
|
||||
.flux .flux_header .date,
|
||||
.flux .flux_content .bottom .date {
|
||||
color: var(--font-color-grey);
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.flux .bottom {
|
||||
|
|
|
@ -798,6 +798,7 @@ a.signin {
|
|||
}
|
||||
|
||||
.flux .flux_header:hover,
|
||||
.flux:not(.current):hover .flux_header .title,
|
||||
.flux.current .flux_header {
|
||||
background-color: var(--background-color-grey-hover);
|
||||
}
|
||||
|
@ -815,8 +816,8 @@ a.signin {
|
|||
border-right: 2px solid var(--border-left-article-unread);
|
||||
}
|
||||
|
||||
.flux .flux_header .item.title a,
|
||||
.flux.not_read:not(.current):hover .flux_header .item.title {
|
||||
.flux .flux_header .item .title a,
|
||||
.flux.not_read:not(.current):hover .flux_header .item .title {
|
||||
color: var(--font-color-link-title);
|
||||
}
|
||||
|
||||
|
@ -828,6 +829,7 @@ a.signin {
|
|||
background-color: var(--background-color-favorite);
|
||||
}
|
||||
|
||||
.flux.favorite:not(.current):hover .flux_header .title,
|
||||
.flux.favorite:not(.current) .flux_header:hover,
|
||||
.flux.favorite.current .flux_header {
|
||||
background-color: var(--background-color-favorite-hover);
|
||||
|
@ -847,9 +849,9 @@ a.signin {
|
|||
padding: 0.425rem;
|
||||
}
|
||||
|
||||
.flux .item.date {
|
||||
.flux .flux_header .date,
|
||||
.flux .flux_content .bottom .date {
|
||||
color: var(--font-color-grey);
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.flux .bottom {
|
||||
|
|
|
@ -751,25 +751,25 @@ form th {
|
|||
.flux .current {
|
||||
background-color: var(--color-background-hover);
|
||||
}
|
||||
.flux .flux_header:hover:not(.current):hover .item.title,
|
||||
.flux .current:not(.current):hover .item.title {
|
||||
.flux .flux_header:hover:not(.current):hover .item .title,
|
||||
.flux .current:not(.current):hover .item .title {
|
||||
background-color: var(--color-background-hover);
|
||||
}
|
||||
.flux.favorite:not(.current) {
|
||||
background-color: var(--color-background-stared);
|
||||
}
|
||||
.flux.favorite:not(.current):hover .item.title {
|
||||
.flux.favorite:not(.current):hover .item .title {
|
||||
background-color: var(--color-background-stared);
|
||||
}
|
||||
.flux.not_read:not(.current) {
|
||||
background-color: var(--color-background-unread);
|
||||
}
|
||||
.flux.not_read:not(.current):hover .item.title {
|
||||
.flux.not_read:not(.current):hover .item .title {
|
||||
background-color: var(--color-background-unread);
|
||||
}
|
||||
.flux .item.date {
|
||||
.flux .flux_header .date,
|
||||
.flux .flux_content .bottom .date {
|
||||
color: var(--color-text-light-darker);
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
.flux .bottom {
|
||||
font-size: 0.8rem;
|
||||
|
@ -909,7 +909,7 @@ a.signin {
|
|||
}
|
||||
|
||||
@media (max-width: 840px) {
|
||||
body:not(.formLogin, .register) .header .item.title {
|
||||
body:not(.formLogin, .register) .header .item .title {
|
||||
display: none;
|
||||
}
|
||||
.form-group .group-name {
|
||||
|
@ -948,7 +948,7 @@ a.signin {
|
|||
width: 35px;
|
||||
text-align: center;
|
||||
}
|
||||
.flux:not(.current):hover .item.title {
|
||||
.flux:not(.current):hover .item .title {
|
||||
top: auto !important;
|
||||
}
|
||||
.aside {
|
||||
|
|
|
@ -751,25 +751,25 @@ form th {
|
|||
.flux .current {
|
||||
background-color: var(--color-background-hover);
|
||||
}
|
||||
.flux .flux_header:hover:not(.current):hover .item.title,
|
||||
.flux .current:not(.current):hover .item.title {
|
||||
.flux .flux_header:hover:not(.current):hover .item .title,
|
||||
.flux .current:not(.current):hover .item .title {
|
||||
background-color: var(--color-background-hover);
|
||||
}
|
||||
.flux.favorite:not(.current) {
|
||||
background-color: var(--color-background-stared);
|
||||
}
|
||||
.flux.favorite:not(.current):hover .item.title {
|
||||
.flux.favorite:not(.current):hover .item .title {
|
||||
background-color: var(--color-background-stared);
|
||||
}
|
||||
.flux.not_read:not(.current) {
|
||||
background-color: var(--color-background-unread);
|
||||
}
|
||||
.flux.not_read:not(.current):hover .item.title {
|
||||
.flux.not_read:not(.current):hover .item .title {
|
||||
background-color: var(--color-background-unread);
|
||||
}
|
||||
.flux .item.date {
|
||||
.flux .flux_header .date,
|
||||
.flux .flux_content .bottom .date {
|
||||
color: var(--color-text-light-darker);
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
.flux .bottom {
|
||||
font-size: 0.8rem;
|
||||
|
@ -909,7 +909,7 @@ a.signin {
|
|||
}
|
||||
|
||||
@media (max-width: 840px) {
|
||||
body:not(.formLogin, .register) .header .item.title {
|
||||
body:not(.formLogin, .register) .header .item .title {
|
||||
display: none;
|
||||
}
|
||||
.form-group .group-name {
|
||||
|
@ -948,7 +948,7 @@ a.signin {
|
|||
width: 35px;
|
||||
text-align: center;
|
||||
}
|
||||
.flux:not(.current):hover .item.title {
|
||||
.flux:not(.current):hover .item .title {
|
||||
top: auto !important;
|
||||
}
|
||||
.aside {
|
||||
|
|
|
@ -969,7 +969,7 @@ form {
|
|||
.current {
|
||||
background-color: var(--color-background-hover);
|
||||
|
||||
&:not(.current):hover .item.title {
|
||||
&:not(.current):hover .item .title {
|
||||
background-color: var(--color-background-hover);
|
||||
}
|
||||
}
|
||||
|
@ -977,7 +977,7 @@ form {
|
|||
&.favorite:not(.current) {
|
||||
background-color: var(--color-background-stared);
|
||||
|
||||
&:hover .item.title {
|
||||
&:hover .item .title {
|
||||
background-color: var(--color-background-stared);
|
||||
}
|
||||
}
|
||||
|
@ -985,16 +985,14 @@ form {
|
|||
&.not_read:not(.current) {
|
||||
background-color: var(--color-background-unread);
|
||||
|
||||
&:hover .item.title {
|
||||
&:hover .item .title {
|
||||
background-color: var(--color-background-unread);
|
||||
}
|
||||
}
|
||||
|
||||
.item {
|
||||
&.date {
|
||||
color: var(--color-text-light-darker);
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
.flux_header .date,
|
||||
.flux_content .bottom .date {
|
||||
color: var(--color-text-light-darker);
|
||||
}
|
||||
|
||||
.bottom {
|
||||
|
@ -1166,7 +1164,7 @@ a.signin {
|
|||
@media (max-width: 840px) {
|
||||
body:not(.formLogin, .register) {
|
||||
.header {
|
||||
.item.title {
|
||||
.item .title {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
@ -1218,7 +1216,7 @@ a.signin {
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
.flux:not(.current):hover .item.title {
|
||||
.flux:not(.current):hover .item .title {
|
||||
top: auto !important;
|
||||
}
|
||||
|
||||
|
|
|
@ -555,10 +555,6 @@ th {
|
|||
padding: 5px;
|
||||
}
|
||||
|
||||
.flux .item.date {
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.flux:not(.current):hover .item.title {
|
||||
}
|
||||
|
||||
|
|
|
@ -555,10 +555,6 @@ th {
|
|||
padding: 5px;
|
||||
}
|
||||
|
||||
.flux .item.date {
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.flux:not(.current):hover .item.title {
|
||||
}
|
||||
|
||||
|
|
|
@ -1263,47 +1263,38 @@ input[type="search"] {
|
|||
width: 200px;
|
||||
}
|
||||
|
||||
.flux:not(.current):hover .websitenone .item.title {
|
||||
max-width: calc(100% - 3 * (2 * var(--frss-padding-flux-items) + 16px));
|
||||
}
|
||||
|
||||
.flux .websiteicon .item.website {
|
||||
width: calc(2 * var(--frss-padding-flux-items) + 16px);
|
||||
}
|
||||
|
||||
.flux:not(.current):hover .websiteicon .item.title {
|
||||
max-width: calc(100% - 4 * (2 * var(--frss-padding-flux-items) + 16px));
|
||||
}
|
||||
|
||||
.flux .websitename .item.website {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.flux:not(.current):hover .websitename .item.title {
|
||||
max-width: calc(100% - 3 * (2 * var(--frss-padding-flux-items) + 16px) - 150px);
|
||||
}
|
||||
|
||||
.website a:hover .favicon,
|
||||
a.website:hover .favicon {
|
||||
filter: grayscale(100%);
|
||||
}
|
||||
|
||||
.flux.not_read .item.title,
|
||||
.flux.current .item.title {
|
||||
.flux.not_read .item .title,
|
||||
.flux.current .item .title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.flux:not(.current):hover .item.title {
|
||||
.flux:not(.current):hover .flux_header .item .title:has(~ .date) {
|
||||
padding-right: 0.5rem;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.flux:not(.current):hover .item .title {
|
||||
background-color: inherit;
|
||||
max-width: calc(100% - 320px);
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.flux:not(.current):hover .item.title.multiline {
|
||||
position: initial;
|
||||
.flux .item:has(.multiline) {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.flux .flux_header .item.title a {
|
||||
.flux .flux_header .item .title a {
|
||||
color: var(--frss-font-color-dark);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
@ -1355,7 +1346,27 @@ a.website:hover .favicon {
|
|||
object-fit: cover;
|
||||
}
|
||||
|
||||
.flux .flux_header .item.title .summary {
|
||||
|
||||
.flux .flux_header .item.titleAuthorSummaryDate {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.flux .flux_header .item .title {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.flux .flux_header .item .title:has(~.date) {
|
||||
padding-right: 155px;
|
||||
}
|
||||
|
||||
.flux .flux_header .item .summary {
|
||||
display: -webkit-box;
|
||||
color: var(--frss-font-color-grey-dark);
|
||||
font-size: 0.9rem;
|
||||
|
@ -1365,18 +1376,28 @@ a.website:hover .favicon {
|
|||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
white-space: break-spaces;
|
||||
margin-top: 2.25rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.flux .flux_header .item.title .author {
|
||||
.flux .flux_header .item .summary:has(~.date) {
|
||||
max-width: 90%;
|
||||
}
|
||||
|
||||
.flux .flux_header .item .title .author {
|
||||
padding-left: 1rem;
|
||||
color: var(--frss-font-color-grey-dark);
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.flux .flux_header .item.date {
|
||||
.flux .flux_header .item .date {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 155px;
|
||||
text-align: right;
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.flux .flux_header .item > a {
|
||||
|
@ -1386,6 +1407,11 @@ a.website:hover .favicon {
|
|||
overflow: hidden;
|
||||
}
|
||||
|
||||
.flux .flux_header .date,
|
||||
.flux .flux_content .bottom .date {
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.item.query > a {
|
||||
display: list-item;
|
||||
list-style-position: inside;
|
||||
|
@ -2180,7 +2206,7 @@ html.slider-active {
|
|||
}
|
||||
|
||||
.flux_header .item.website span,
|
||||
.item.date, .day .date,
|
||||
.item .date, .day .date,
|
||||
.dropdown-menu > .no-mobile,
|
||||
.no-mobile {
|
||||
display: none;
|
||||
|
@ -2223,6 +2249,10 @@ html.slider-active {
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
.flux .flux_header .item .title:has(~.date) {
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
#overlay .close {
|
||||
position: relative;
|
||||
}
|
||||
|
|
|
@ -1263,47 +1263,38 @@ input[type="search"] {
|
|||
width: 200px;
|
||||
}
|
||||
|
||||
.flux:not(.current):hover .websitenone .item.title {
|
||||
max-width: calc(100% - 3 * (2 * var(--frss-padding-flux-items) + 16px));
|
||||
}
|
||||
|
||||
.flux .websiteicon .item.website {
|
||||
width: calc(2 * var(--frss-padding-flux-items) + 16px);
|
||||
}
|
||||
|
||||
.flux:not(.current):hover .websiteicon .item.title {
|
||||
max-width: calc(100% - 4 * (2 * var(--frss-padding-flux-items) + 16px));
|
||||
}
|
||||
|
||||
.flux .websitename .item.website {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.flux:not(.current):hover .websitename .item.title {
|
||||
max-width: calc(100% - 3 * (2 * var(--frss-padding-flux-items) + 16px) - 150px);
|
||||
}
|
||||
|
||||
.website a:hover .favicon,
|
||||
a.website:hover .favicon {
|
||||
filter: grayscale(100%);
|
||||
}
|
||||
|
||||
.flux.not_read .item.title,
|
||||
.flux.current .item.title {
|
||||
.flux.not_read .item .title,
|
||||
.flux.current .item .title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.flux:not(.current):hover .item.title {
|
||||
.flux:not(.current):hover .flux_header .item .title:has(~ .date) {
|
||||
padding-left: 0.5rem;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.flux:not(.current):hover .item .title {
|
||||
background-color: inherit;
|
||||
max-width: calc(100% - 320px);
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.flux:not(.current):hover .item.title.multiline {
|
||||
position: initial;
|
||||
.flux .item:has(.multiline) {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.flux .flux_header .item.title a {
|
||||
.flux .flux_header .item .title a {
|
||||
color: var(--frss-font-color-dark);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
@ -1355,7 +1346,27 @@ a.website:hover .favicon {
|
|||
object-fit: cover;
|
||||
}
|
||||
|
||||
.flux .flux_header .item.title .summary {
|
||||
|
||||
.flux .flux_header .item.titleAuthorSummaryDate {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.flux .flux_header .item .title {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.flux .flux_header .item .title:has(~.date) {
|
||||
padding-left: 155px;
|
||||
}
|
||||
|
||||
.flux .flux_header .item .summary {
|
||||
display: -webkit-box;
|
||||
color: var(--frss-font-color-grey-dark);
|
||||
font-size: 0.9rem;
|
||||
|
@ -1365,18 +1376,28 @@ a.website:hover .favicon {
|
|||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
white-space: break-spaces;
|
||||
margin-top: 2.25rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.flux .flux_header .item.title .author {
|
||||
.flux .flux_header .item .summary:has(~.date) {
|
||||
max-width: 90%;
|
||||
}
|
||||
|
||||
.flux .flux_header .item .title .author {
|
||||
padding-right: 1rem;
|
||||
color: var(--frss-font-color-grey-dark);
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.flux .flux_header .item.date {
|
||||
.flux .flux_header .item .date {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 155px;
|
||||
text-align: left;
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.flux .flux_header .item > a {
|
||||
|
@ -1386,6 +1407,11 @@ a.website:hover .favicon {
|
|||
overflow: hidden;
|
||||
}
|
||||
|
||||
.flux .flux_header .date,
|
||||
.flux .flux_content .bottom .date {
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.item.query > a {
|
||||
display: list-item;
|
||||
list-style-position: inside;
|
||||
|
@ -2180,7 +2206,7 @@ html.slider-active {
|
|||
}
|
||||
|
||||
.flux_header .item.website span,
|
||||
.item.date, .day .date,
|
||||
.item .date, .day .date,
|
||||
.dropdown-menu > .no-mobile,
|
||||
.no-mobile {
|
||||
display: none;
|
||||
|
@ -2223,6 +2249,10 @@ html.slider-active {
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
.flux .flux_header .item .title:has(~.date) {
|
||||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
#overlay .close {
|
||||
position: relative;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue