Przeglądaj źródła

Improve authors (#2025)

* Links for authors and multiple authors

Favour ';' as a separator instead of ',' to better cope with
multi-author scientific articles.
Follow-up of https://github.com/FreshRSS/FreshRSS/pull/1997 ,
https://github.com/FreshRSS/FreshRSS/issues/1968,
https://github.com/FreshRSS/FreshRSS/pull/2023

* Change i18n authors

* Update layout

* Unicode-compatible search

Example for `author:Loïc`

* author <em> styling

* Final details

* Minor spacing
Alexandre Alapetite 7 lat temu
rodzic
commit
b323ed0846

+ 1 - 1
app/Models/Category.php

@@ -68,7 +68,7 @@ class FreshRSS_Category extends Minz_Model {
 		$this->id = $value;
 	}
 	public function _name($value) {
-		$this->name = mb_strcut(trim($value), 0, 255, 'UTF-8');
+		$this->name = trim($value);
 	}
 	public function _feeds($values) {
 		if (!is_array($values)) {

+ 22 - 12
app/Models/Entry.php

@@ -10,7 +10,7 @@ class FreshRSS_Entry extends Minz_Model {
 	private $id = 0;
 	private $guid;
 	private $title;
-	private $author;
+	private $authors;
 	private $content;
 	private $link;
 	private $date;
@@ -21,17 +21,16 @@ class FreshRSS_Entry extends Minz_Model {
 	private $feed;
 	private $tags;
 
-	public function __construct($feedId = '', $guid = '', $title = '', $author = '', $content = '',
+	public function __construct($feedId = '', $guid = '', $title = '', $authors = '', $content = '',
 	                            $link = '', $pubdate = 0, $is_read = false, $is_favorite = false, $tags = '') {
 		$this->_title($title);
-		$this->_author($author);
+		$this->_authors($authors);
 		$this->_content($content);
 		$this->_link($link);
 		$this->_date($pubdate);
 		$this->_isRead($is_read);
 		$this->_isFavorite($is_favorite);
 		$this->_feedId($feedId);
-		$tags = mb_strcut($tags, 0, 1023, 'UTF-8');
 		$this->_tags($tags);
 		$this->_guid($guid);
 	}
@@ -45,8 +44,12 @@ class FreshRSS_Entry extends Minz_Model {
 	public function title() {
 		return $this->title;
 	}
-	public function author() {
-		return $this->author === null ? '' : $this->author;
+	public function authors($asString = false) {
+		if ($asString) {
+			return $this->authors == null ? '' : ';' . implode('; ', $this->authors);
+		} else {
+			return $this->authors;
+		}
 	}
 	public function content() {
 		return $this->content;
@@ -88,7 +91,7 @@ class FreshRSS_Entry extends Minz_Model {
 	}
 	public function tags($asString = false) {
 		if ($asString) {
-			return $this->tags == '' ? '' : '#' . implode(' #', $this->tags);
+			return $this->tags == null ? '' : '#' . implode(' #', $this->tags);
 		} else {
 			return $this->tags;
 		}
@@ -97,7 +100,7 @@ class FreshRSS_Entry extends Minz_Model {
 	public function hash() {
 		if ($this->hash === null) {
 			//Do not include $this->date because it may be automatically generated when lacking
-			$this->hash = md5($this->link . $this->title . $this->author . $this->content . $this->tags(true));
+			$this->hash = md5($this->link . $this->title . $this->authors(true) . $this->content . $this->tags(true));
 		}
 		return $this->hash;
 	}
@@ -124,11 +127,18 @@ class FreshRSS_Entry extends Minz_Model {
 	}
 	public function _title($value) {
 		$this->hash = null;
-		$this->title = mb_strcut($value, 0, 255, 'UTF-8');
+		$this->title = $value;
 	}
-	public function _author($value) {
+	public function _authors($value) {
 		$this->hash = null;
-		$this->author = mb_strcut($value, 0, 255, 'UTF-8');
+		if (!is_array($value)) {
+			if (strpos($value, ';') !== false) {
+				$value = preg_split('/\s*[;]\s*/', $value, -1, PREG_SPLIT_NO_EMPTY);
+			} else {
+				$value = preg_split('/\s*[,]\s*/', $value, -1, PREG_SPLIT_NO_EMPTY);
+			}
+		}
+		$this->authors = $value;
 	}
 	public function _content($value) {
 		$this->hash = null;
@@ -280,7 +290,7 @@ class FreshRSS_Entry extends Minz_Model {
 			'id' => $this->id(),
 			'guid' => $this->guid(),
 			'title' => $this->title(),
-			'author' => $this->author(),
+			'author' => $this->authors(true),
 			'content' => $this->content(),
 			'link' => $this->link(),
 			'date' => $this->date(true),

+ 1 - 1
app/Models/Feed.php

@@ -420,7 +420,7 @@ class FreshRSS_Feed extends Minz_Model {
 			$author_names = '';
 			if (is_array($authors)) {
 				foreach ($authors as $author) {
-					$author_names .= html_only_entity_decode(strip_tags($author->name == '' ? $author->email : $author->name)) . ', ';
+					$author_names .= html_only_entity_decode(strip_tags($author->name == '' ? $author->email : $author->name)) . '; ';
 				}
 			}
 			$author_names = substr($author_names, 0, -2);

+ 4 - 4
app/Models/Search.php

@@ -141,7 +141,7 @@ class FreshRSS_Search {
 			$this->intitle = $matches['search'];
 			$input = str_replace($matches[0], '', $input);
 		}
-		if (preg_match_all('/\bintitle:(?P<search>[\w+]*)/', $input, $matches)) {
+		if (preg_match_all('/\bintitle:(?P<search>[^\s"]*)/', $input, $matches)) {
 			$this->intitle = array_merge($this->intitle ? $this->intitle : array(), $matches['search']);
 			$input = str_replace($matches[0], '', $input);
 		}
@@ -155,7 +155,7 @@ class FreshRSS_Search {
 			$this->not_intitle = $matches['search'];
 			$input = str_replace($matches[0], '', $input);
 		}
-		if (preg_match_all('/[!-]intitle:(?P<search>[\w+]*)/', $input, $matches)) {
+		if (preg_match_all('/[!-]intitle:(?P<search>[^\s"]*)/', $input, $matches)) {
 			$this->not_intitle = array_merge($this->not_intitle ? $this->not_intitle : array(), $matches['search']);
 			$input = str_replace($matches[0], '', $input);
 		}
@@ -179,7 +179,7 @@ class FreshRSS_Search {
 			$this->author = $matches['search'];
 			$input = str_replace($matches[0], '', $input);
 		}
-		if (preg_match_all('/\bauthor:(?P<search>[\w+]*)/', $input, $matches)) {
+		if (preg_match_all('/\bauthor:(?P<search>[^\s"]*)/', $input, $matches)) {
 			$this->author = array_merge($this->author ? $this->author : array(), $matches['search']);
 			$input = str_replace($matches[0], '', $input);
 		}
@@ -193,7 +193,7 @@ class FreshRSS_Search {
 			$this->not_author = $matches['search'];
 			$input = str_replace($matches[0], '', $input);
 		}
-		if (preg_match_all('/[!-]author:(?P<search>[\w+]*)/', $input, $matches)) {
+		if (preg_match_all('/[!-]author:(?P<search>[^\s"]*)/', $input, $matches)) {
 			$this->not_author = array_merge($this->not_author ? $this->not_author : array(), $matches['search']);
 			$input = str_replace($matches[0], '', $input);
 		}

+ 1 - 1
app/i18n/cz/gen.php

@@ -181,7 +181,7 @@ return array(
 	'short' => array(
 		'attention' => 'Upozornění!',
 		'blank_to_disable' => 'Zakázat - ponechte prázdné',
-		'by_author' => 'Od <em>%s</em>',
+		'by_author' => 'Od:',
 		'by_default' => 'Výchozí',
 		'damn' => 'Sakra!',
 		'default_category' => 'Nezařazeno',

+ 1 - 1
app/i18n/de/gen.php

@@ -180,7 +180,7 @@ return array(
 	'short' => array(
 		'attention' => 'Achtung!',
 		'blank_to_disable' => 'Zum Deaktivieren frei lassen',
-		'by_author' => 'Von <em>%s</em>',
+		'by_author' => 'Von:',
 		'by_default' => 'standardmäßig',
 		'damn' => 'Verdammt!',
 		'default_category' => 'Unkategorisiert',

+ 1 - 1
app/i18n/en/gen.php

@@ -181,7 +181,7 @@ return array(
 	'short' => array(
 		'attention' => 'Warning!',
 		'blank_to_disable' => 'Leave blank to disable',
-		'by_author' => 'By <em>%s</em>',
+		'by_author' => 'By:',
 		'by_default' => 'By default',
 		'damn' => 'Blast!',
 		'default_category' => 'Uncategorized',

+ 1 - 1
app/i18n/es/gen.php

@@ -181,7 +181,7 @@ return array(
 	'short' => array(
 		'attention' => '¡Aviso!',
 		'blank_to_disable' => 'Deja en blanco para desactivar',
-		'by_author' => 'Por <em>%s</em>',
+		'by_author' => 'Por:',
 		'by_default' => 'Por defecto',
 		'damn' => '¡Córcholis!',
 		'default_category' => 'Sin categorizar',

+ 1 - 1
app/i18n/fr/gen.php

@@ -181,7 +181,7 @@ return array(
 	'short' => array(
 		'attention' => 'Attention !',
 		'blank_to_disable' => 'Laissez vide pour désactiver',
-		'by_author' => 'Par <em>%s</em>',
+		'by_author' => 'Par :',
 		'by_default' => 'Par défaut',
 		'damn' => 'Arf !',
 		'default_category' => 'Sans catégorie',

+ 1 - 1
app/i18n/he/gen.php

@@ -181,7 +181,7 @@ return array(
 	'short' => array(
 		'attention' => 'זהירות!',
 		'blank_to_disable' => 'יש להשאיר ריק על מנת לנטרל',
-		'by_author' => 'מאת <em>%s</em>',
+		'by_author' => 'מאת :',
 		'by_default' => 'ברירת מחדל',
 		'damn' => 'הו לא!',
 		'default_category' => 'ללא קטגוריה',

+ 1 - 1
app/i18n/it/gen.php

@@ -181,7 +181,7 @@ return array(
 	'short' => array(
 		'attention' => 'Attenzione!',
 		'blank_to_disable' => 'Lascia vuoto per disabilitare',
-		'by_author' => 'di <em>%s</em>',
+		'by_author' => 'di:',
 		'by_default' => 'predefinito',
 		'damn' => 'Ops!',
 		'default_category' => 'Senza categoria',

+ 1 - 1
app/i18n/kr/gen.php

@@ -181,7 +181,7 @@ return array(
 	'short' => array(
 		'attention' => '경고!',
 		'blank_to_disable' => '빈 칸으로 두면 비활성화',
-		'by_author' => 'By <em>%s</em>',
+		'by_author' => 'By:',
 		'by_default' => '기본값',
 		'damn' => '이런!',
 		'default_category' => '분류 없음',

+ 1 - 1
app/i18n/nl/gen.php

@@ -181,7 +181,7 @@ return array(
 	'short' => array(
 		'attention' => 'Attentie!',
 		'blank_to_disable' => 'Laat leeg om uit te zetten',
-		'by_author' => 'Door <em>%s</em>',
+		'by_author' => 'Door:',
 		'by_default' => 'Door standaard',
 		'damn' => 'Potverdorie!',
 		'default_category' => 'Niet ingedeeld',

+ 1 - 1
app/i18n/pt-br/gen.php

@@ -180,7 +180,7 @@ return array(
 	'short' => array(
 		'attention' => 'Atencão!',
 		'blank_to_disable' => 'Deixe em branco para desativar',
-		'by_author' => 'Por <em>%s</em>',
+		'by_author' => 'Por:',
 		'by_default' => 'Por padrão',
 		'damn' => 'Buumm!',
 		'default_category' => 'Sem categoria',

+ 1 - 1
app/i18n/ru/gen.php

@@ -181,7 +181,7 @@ return array(
 	'short' => array(
 		'attention' => 'Warning!',
 		'blank_to_disable' => 'Leave blank to disable',
-		'by_author' => 'By <em>%s</em>',
+		'by_author' => 'By:',
 		'by_default' => 'By default',
 		'damn' => 'Damn!',
 		'default_category' => 'Uncategorized',

+ 1 - 1
app/i18n/tr/gen.php

@@ -181,7 +181,7 @@ return array(
 	'short' => array(
 		'attention' => 'Tehlike!',
 		'blank_to_disable' => 'Devredışı bırakmak için boş bırakın',
-		'by_author' => '<em>%s</em> tarafından',
+		'by_author' => 'Tarafından:',
 		'by_default' => 'Öntanımlı',
 		'damn' => 'Hay aksi!',
 		'default_category' => 'Kategorisiz',

+ 1 - 1
app/i18n/zh-cn/gen.php

@@ -181,7 +181,7 @@ return array(
 	'short' => array(
 		'attention' => '警告!',
 		'blank_to_disable' => '留空以禁用',
-		'by_author' => '作者 <em>%s</em>',
+		'by_author' => '作者',
 		'by_default' => '默认',
 		'damn' => '错误!',
 		'default_category' => '未分类',

+ 1 - 1
app/views/configure/display.phtml

@@ -39,7 +39,7 @@
 								<?php } ?>
 							</div>
 							<div class="properties">
-								<div><?php echo sprintf('%s — %s', $theme['name'], _t('gen.short.by_author', $theme['author'])); ?></div>
+								<div><?php echo sprintf('%s — %s %s', $theme['name'], _t('gen.short.by_author'), $theme['author']); ?></div>
 								<div><?php echo $theme['description'] ?></div>
 								<div class="page-number"><?php echo sprintf('%d/%d', $i, $slides) ?></div>
 							</div>

+ 1 - 1
app/views/helpers/export/articles.phtml

@@ -34,7 +34,7 @@ foreach ($this->entriesRaw as $entryRaw) {
 		'id' => $entry->guid(),
 		'categories' => array_values($entry->tags()),
 		'title' => $entry->title(),
-		'author' => $entry->author(),
+		'author' => $entry->authors(true),	//TODO: Make an array like tags?
 		'published' => $entry->date(true),
 		'updated' => $entry->date(true),
 		'alternate' => array(array(

+ 1 - 1
app/views/helpers/extension/configure.phtml

@@ -5,7 +5,7 @@
 		                                         : _t('admin.extensions.disabled'); ?>
 	</h1>
 
-	<p class="alert alert-warn"><?php echo $this->extension->getDescription(); ?> — <?php echo _t('gen.short.by_author', $this->extension->getAuthor()); ?></p>
+	<p class="alert alert-warn"><?php echo $this->extension->getDescription(); ?> — <?php echo _t('gen.short.by_author'), ' ', $this->extension->getAuthor(); ?></p>
 
 	<h2><?php echo _t('gen.action.manage'); ?></h2>
 	<?php

+ 13 - 4
app/views/index/normal.phtml

@@ -67,10 +67,19 @@ if (!empty($this->entries)) {
 		?><div class="flux_content">
 			<div class="content <?php echo $content_width; ?>">
 				<h1 class="title"><a target="_blank" rel="noreferrer" class="go_website" href="<?php echo $this->entry->link(); ?>"><?php echo $this->entry->title(); ?></a></h1>
-				<?php
-					$author = $this->entry->author();
-					echo $author != '' ? '<div class="author">' . _t('gen.short.by_author', $author) . '</div>' : '',
-						$lazyload && $hidePosts ? lazyimg($this->entry->content()) : $this->entry->content();
+				<div class="author"><?php
+					$authors = $this->entry->authors();
+					if (is_array($authors)):
+						$first = true;
+						foreach ($authors as $author):
+							echo $first ? _t('gen.short.by_author') . ' ' : '· ';
+							$first = false;
+				?>
+<em><a href="<?php echo _url('index', 'index', 'search', 'author:' . str_replace(' ', '+', htmlspecialchars_decode($author, ENT_QUOTES))); ?>"><?php echo $author; ?></a></em>
+				<?php	endforeach; ?>
+				</div><?php
+					endif;
+					echo $lazyload && $hidePosts ? lazyimg($this->entry->content()) : $this->entry->content();
 				?>
 			</div><?php
 

+ 13 - 3
app/views/index/reader.phtml

@@ -39,9 +39,19 @@ if (!empty($this->entries)) {
 				<h1 class="title"><a target="_blank" rel="noreferrer" class="go_website" href="<?php echo $item->link(); ?>"><?php echo $item->title(); ?></a></h1>
 
 				<div class="author"><?php
-					$author = $item->author();
-					echo $author != '' ? _t('gen.short.by_author', $author) . ' — ' : '',
-						$item->date();
+					$authors = $item->authors();
+					if (is_array($authors)):
+						$first = true;
+						foreach ($authors as $author):
+							echo $first ? _t('gen.short.by_author') . ' ' : '· ';
+							$first = false;
+				?>
+<em><a href="<?php echo _url('index', 'index', 'search', 'author:' . str_replace(' ', '+', htmlspecialchars_decode($author, ENT_QUOTES))); ?>"><?php echo $author; ?></a></em>
+				<?php
+						endforeach;
+						echo ' — ';
+					endif;
+					echo $item->date();
 				?></div>
 
 				<?php echo $item->content(); ?>

+ 14 - 4
app/views/index/rss.phtml

@@ -13,10 +13,20 @@ foreach ($this->entries as $item) {
 		<item>
 			<title><?php echo $item->title(); ?></title>
 			<link><?php echo $item->link(); ?></link>
-			<?php $author = $item->author(); ?>
-			<?php if ($author != '') { ?>
-			<dc:creator><?php echo $author; ?></dc:creator>
-			<?php } ?>
+			<?php
+				$authors = $item->authors();
+				if (is_array($authors)) {
+					foreach ($authors as $author) {
+						echo "\t\t\t" , '<author>', $author, '</author>', "\n";
+					}
+				}
+				$categories = $item->tags();
+				if (is_array($categories)) {
+					foreach ($categories as $category) {
+						echo "\t\t\t" , '<category>', $category, '</category>', "\n";
+					}
+				}
+			?>
 			<description><![CDATA[<?php
 	echo $item->content();
 ?>]]></description>

+ 5 - 4
p/api/fever.php

@@ -3,6 +3,7 @@
  * Fever API for FreshRSS
  * Version 0.1
  * Author: Kevin Papst / https://github.com/kevinpapst
+ * Documentation: https://feedafever.com/api
  *
  * Inspired by:
  * TinyTinyRSS Fever API plugin @dasmurphy
@@ -63,7 +64,7 @@ class FeverDAO extends Minz_ModelPdo
 
 		$sql = 'SELECT id, guid, title, author, '
 			. ($entryDAO->isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content')
-			. ', link, date, is_read, is_favorite, id_feed, tags '
+			. ', link, date, is_read, is_favorite, id_feed '
 			. 'FROM `' . $this->prefix . 'entry` WHERE';
 
 		if (!empty($entry_ids)) {
@@ -495,17 +496,17 @@ class FeverAPI
 		// Load list of extensions and enable the "system" ones.
 		Minz_ExtensionManager::init();
 
-		foreach($entries as $item) {
+		foreach ($entries as $item) {
 			/** @var FreshRSS_Entry $entry */
 			$entry = Minz_ExtensionManager::callHook('entry_before_display', $item);
-			if (is_null($entry)) {
+			if ($entry == null) {
 				continue;
 			}
 			$items[] = array(
 				'id' => $entry->id(),
 				'feed_id' => $entry->feed(false),
 				'title' => $entry->title(),
-				'author' => $entry->author(),
+				'author' => $entry->authors(true),
 				'html' => $entry->content(),
 				'url' => $entry->link(),
 				'is_saved' => $entry->isFavorite() ? 1 : 0,

+ 5 - 2
p/api/greader.php

@@ -18,6 +18,7 @@ Server-side API compatible with Google Reader API layer 2
 * https://github.com/ericmann/gReader-Library/blob/master/greader.class.php
 * https://github.com/devongovett/reader
 * https://github.com/theoldreader/api
+* https://www.inoreader.com/developers/
 */
 
 require(__DIR__ . '/../../constants.php');
@@ -471,6 +472,7 @@ function entriesToArray($entries) {
 			'categories' => array(
 				'user/-/state/com.google/reading-list',
 				'user/-/label/' . $c_name,
+				//TODO: Add other tags
 			),
 			'origin' => array(
 				'streamId' => 'feed/' . $f_id,
@@ -478,8 +480,9 @@ function entriesToArray($entries) {
 				//'htmlUrl' => $line['f_website'],
 			),
 		);
-		if ($entry->author() != '') {
-			$item['author'] = $entry->author();
+		$author = $entry->authors(true);
+		if ($author != '') {
+			$item['author'] = $author;
 		}
 		if ($entry->isRead()) {
 			$item['categories'][] = 'user/-/state/com.google/read';

+ 4 - 2
p/scripts/main.js

@@ -731,7 +731,7 @@ function init_shortcuts() {
 
 function init_stream(divStream) {
 	divStream.on('click', '.flux_header,.flux_content', function (e) {	//flux_toggle
-		if ($(e.target).closest('.content, .item.website, .item.link').length > 0) {
+		if ($(e.target).closest('.keep_unread, .content, .item.website, .item.link, .dropdown-menu').length > 0) {
 			return;
 		}
 		if (!context.sides_close_article && $(e.target).is('div.flux_content')) {
@@ -788,7 +788,9 @@ function init_stream(divStream) {
 	});
 
 	divStream.on('click', '.flux .content a', function () {
-		$(this).attr('target', '_blank').attr('rel', 'noreferrer');
+		if (!$(this).closest('div').hasClass('author')) {
+			$(this).attr('target', '_blank').attr('rel', 'noreferrer');
+		}
 	});
 
 	if (context.auto_mark_site) {