From b790b160c4782405d3cec506a9775b3c4f1d8df1 Mon Sep 17 00:00:00 2001 From: Patrick Crandol Date: Sun, 28 Oct 2018 20:28:30 -0400 Subject: Add ID's to buttons in configure-feed Add ID's to be able to individually target buttons inside the configure feeds (Subscriptions Management & Import/Export) buttons. --- app/layout/aside_feed.phtml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/layout/aside_feed.phtml b/app/layout/aside_feed.phtml index ce029cfa0..7b1645ebd 100644 --- a/app/layout/aside_feed.phtml +++ b/app/layout/aside_feed.phtml @@ -12,8 +12,8 @@
- - + +
-- cgit v1.2.3 From 396c714df9894ed053030d8a9daedcf95c397960 Mon Sep 17 00:00:00 2001 From: Quentí <33203663+Quenty31@users.noreply.github.com> Date: Sun, 4 Nov 2018 13:46:08 +0100 Subject: [i18n] Add the Occitan locale (#2110) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Create oc.php L27 : ajout de l’occitan * Create admin.php * Create conf.php * Create feedback.php * Create gen.php * Create index.php * Create install.php * Create sub.php * Delete conf.php * Create conf.php * Update gen.php * Update gen.php * Update gen.php * Update gen.php * Update gen.php * Update gen.php * Update gen.php * Update gen.php * Update gen.php * Update gen.php * Update gen.php * Update gen.php * Update gen.php * Update gen.php * Update oc.php * Update admin.php * Update conf.php * Update feedback.php * Update gen.php * Update index.php * Update install.php * Update sub.php * Update oc.php --- app/i18n/cz/gen.php | 1 + app/i18n/de/gen.php | 1 + app/i18n/en/gen.php | 1 + app/i18n/es/gen.php | 1 + app/i18n/fr/gen.php | 1 + app/i18n/he/gen.php | 1 + app/i18n/it/gen.php | 1 + app/i18n/kr/gen.php | 1 + app/i18n/nl/gen.php | 1 + app/i18n/oc/admin.php | 194 +++++++++++++++++++++++++++++++++++++++++++++++ app/i18n/oc/conf.php | 183 ++++++++++++++++++++++++++++++++++++++++++++ app/i18n/oc/feedback.php | 115 ++++++++++++++++++++++++++++ app/i18n/oc/gen.php | 194 +++++++++++++++++++++++++++++++++++++++++++++++ app/i18n/oc/index.php | 61 +++++++++++++++ app/i18n/oc/install.php | 124 ++++++++++++++++++++++++++++++ app/i18n/oc/sub.php | 84 ++++++++++++++++++++ app/i18n/pt-br/gen.php | 1 + app/i18n/ru/gen.php | 1 + app/i18n/tr/gen.php | 1 + app/i18n/zh-cn/gen.php | 1 + cli/i18n/ignore/oc.php | 56 ++++++++++++++ 21 files changed, 1024 insertions(+) create mode 100644 app/i18n/oc/admin.php create mode 100644 app/i18n/oc/conf.php create mode 100644 app/i18n/oc/feedback.php create mode 100644 app/i18n/oc/gen.php create mode 100644 app/i18n/oc/index.php create mode 100644 app/i18n/oc/install.php create mode 100644 app/i18n/oc/sub.php create mode 100644 cli/i18n/ignore/oc.php (limited to 'app') diff --git a/app/i18n/cz/gen.php b/app/i18n/cz/gen.php index b9a65f210..eab572be4 100644 --- a/app/i18n/cz/gen.php +++ b/app/i18n/cz/gen.php @@ -124,6 +124,7 @@ return array( 'it' => 'Italiano', 'kr' => '한국어', 'nl' => 'Nederlands', + 'oc' => 'Occitan', 'pt-br' => 'Português (Brasil)', 'ru' => 'Русский', 'tr' => 'Türkçe', diff --git a/app/i18n/de/gen.php b/app/i18n/de/gen.php index 617b2a494..935ab6a75 100644 --- a/app/i18n/de/gen.php +++ b/app/i18n/de/gen.php @@ -124,6 +124,7 @@ return array( 'it' => 'Italiano', 'kr' => '한국어', 'nl' => 'Nederlands', + 'oc' => 'Occitan', 'pt-br' => 'Português (Brasil)', 'ru' => 'Русский', 'tr' => 'Türkçe', diff --git a/app/i18n/en/gen.php b/app/i18n/en/gen.php index 9f7da55a5..9328a35ec 100644 --- a/app/i18n/en/gen.php +++ b/app/i18n/en/gen.php @@ -124,6 +124,7 @@ return array( 'it' => 'Italiano', 'kr' => '한국어', 'nl' => 'Nederlands', + 'oc' => 'Occitan', 'pt-br' => 'Português (Brasil)', 'ru' => 'Русский', 'tr' => 'Türkçe', diff --git a/app/i18n/es/gen.php b/app/i18n/es/gen.php index fe3d62e2d..35a01c02d 100755 --- a/app/i18n/es/gen.php +++ b/app/i18n/es/gen.php @@ -124,6 +124,7 @@ return array( 'it' => 'Italiano', 'kr' => '한국어', 'nl' => 'Nederlands', + 'oc' => 'Occitan', 'pt-br' => 'Português (Brasil)', 'ru' => 'Русский', 'tr' => 'Türkçe', diff --git a/app/i18n/fr/gen.php b/app/i18n/fr/gen.php index 1e1cef590..34395a380 100644 --- a/app/i18n/fr/gen.php +++ b/app/i18n/fr/gen.php @@ -124,6 +124,7 @@ return array( 'it' => 'Italiano', 'kr' => '한국어', 'nl' => 'Nederlands', + 'oc' => 'Occitan', 'pt-br' => 'Português (Brasil)', 'ru' => 'Русский', 'tr' => 'Türkçe', diff --git a/app/i18n/he/gen.php b/app/i18n/he/gen.php index 26b8f99e6..64050dc4f 100644 --- a/app/i18n/he/gen.php +++ b/app/i18n/he/gen.php @@ -124,6 +124,7 @@ return array( 'it' => 'Italiano', 'kr' => '한국어', 'nl' => 'Nederlands', + 'oc' => 'Occitan', 'pt-br' => 'Português (Brasil)', 'ru' => 'Русский', 'tr' => 'Türkçe', diff --git a/app/i18n/it/gen.php b/app/i18n/it/gen.php index ab17441e7..200ce813a 100644 --- a/app/i18n/it/gen.php +++ b/app/i18n/it/gen.php @@ -124,6 +124,7 @@ return array( 'it' => 'Italiano', 'kr' => '한국어', 'nl' => 'Nederlands', + 'oc' => 'Occitan', 'pt-br' => 'Português (Brasil)', 'ru' => 'Русский', 'tr' => 'Türkçe', diff --git a/app/i18n/kr/gen.php b/app/i18n/kr/gen.php index 6a461bdac..f87f24b64 100644 --- a/app/i18n/kr/gen.php +++ b/app/i18n/kr/gen.php @@ -124,6 +124,7 @@ return array( 'it' => 'Italiano', 'kr' => '한국어', 'nl' => 'Nederlands', + 'oc' => 'Occitan', 'pt-br' => 'Português (Brasil)', 'ru' => 'Русский', 'tr' => 'Türkçe', diff --git a/app/i18n/nl/gen.php b/app/i18n/nl/gen.php index fdc4338c3..f18da5c36 100644 --- a/app/i18n/nl/gen.php +++ b/app/i18n/nl/gen.php @@ -124,6 +124,7 @@ return array( 'it' => 'Italiano', 'kr' => '한국어', 'nl' => 'Nederlands', + 'oc' => 'Occitan', 'pt-br' => 'Português (Brasil)', 'ru' => 'Русский', 'tr' => 'Türkçe', diff --git a/app/i18n/oc/admin.php b/app/i18n/oc/admin.php new file mode 100644 index 000000000..3bad14e7c --- /dev/null +++ b/app/i18n/oc/admin.php @@ -0,0 +1,194 @@ + array( + 'disabled' => 'Desactivada', + 'enabled' => 'Activada', + 'name' => 'Nom', + 'description' => 'Descripcion', + 'empty_list' => 'Cap d’extensions pas installadas', + 'no_configure_view' => 'Aquesta extension se pòt pas configurar.', + 'system' => array( + '_' => 'Extensions sistèma', + 'no_rights' => 'Extensions sistèma (contrarotlat per l’administrator)', + ), + 'title' => 'Extensions', + 'user' => 'Extensions utilizaire', + 'community' => 'Extensions utilizaires disponiblas', + 'version' => 'Version', + 'author' => 'Autor', + 'latest' => 'Installada', + 'update' => 'Mesa a jorn disponibla', + ), + 'stats' => array( + 'no_idle' => 'I a pas cap d’article inactiu !', + 'status_favorites' => 'Favorits', + 'status_read' => 'Legit', + '_' => 'Estatisticas', + 'all_feeds' => 'Totes los fluxes', + 'category' => 'Categoria', + 'entry_count' => 'Nombre d’articles', + 'entry_per_category' => 'Articles per categoria', + 'entry_per_day' => 'Nombre d’articles per jorn (30 darrièrs jorns)', + 'entry_per_day_of_week' => 'Per jorn de la setmana (mejana : %.2f messatges)', + 'entry_per_hour' => 'Per ora (mejana : %.2f messatges)', + 'entry_per_month' => 'Per mes (mejana : %.2f messatges)', + 'entry_repartition' => 'Reparticion dels articles', + 'feed' => 'Flux', + 'feed_per_category' => 'Fluxes per categoria', + 'idle' => 'Fluxes inactius', + 'main' => 'Estatisticas principalas', + 'main_stream' => 'Flux màger', + 'menu' => array( + 'idle' => 'Fluxes inactius', + 'main' => 'Estatisticas principalas', + 'repartition' => 'Reparticion dels articles', + ), + 'number_entries' => '%d articles', + 'percent_of_total' => '%% del total', + 'repartition' => 'Reparticion dels articles', + 'status_total' => 'Total', + 'status_unread' => 'Pas legits', + 'title' => 'Estatisticas', + 'top_feed' => 'Los dètz fluxes mai gròsses', + ), + 'system' => array( + '_' => 'Configuracion sistèma', + 'instance-name' => 'Nom de l’instància', + 'registration' => array( + 'number' => 'Nombre max de comptes', + 'help' => '0 vòl dire qu’i a pas cap de limita de compte', + ), + 'auto-update-url' => 'URL del servici de mesa a jorn', + 'max-categories' => 'Limita de categoria per utilizaire', + 'max-feeds' => 'Limita de fluxes per utilizaire', + ), + 'update' => array( + 'apply' => 'Aplicar', + 'none' => 'Cap d’actualizacion d’aplicar', + '_' => 'Sistèma de mesa a jorn', + 'check' => 'Verificar las mesas a jorn', + 'current_version' => 'Vòstra version actuala de FreshRSS es %s.', + 'last' => 'Darrièra verificacion : %s', + 'title' => 'Sistèma de mesa a jorn', + ), + 'user' => array( + 'delete_users' => 'Suprimir un utilizaire', + 'language' => 'Lenga', + 'password_format' => 'Almens 7 caractèrs', + 'selected' => 'Utilizaire seleccionat', + 'username' => 'Nom d’utilizaire', + 'users' => 'Utilizaires', + 'articles_and_size' => '%s articles (%s)', + 'create' => 'Crear un nòu utilizaire', + 'number' => '%d compte ja creat', + 'numbers' => '%d comptes ja creats', + 'password_form' => 'Senhal
(ex. : per la connexion via formulari)', + 'title' => 'Gestion dels utilizaires', + 'update_users' => 'Actualizar un utilizaire', + 'user_list' => 'Lista dels utilizaires', + ), + 'auth' => array( + 'none' => 'Cap (perilhós)', + 'allow_anonymous' => 'Autorizar la lectura anonima dels articles de l’utilizaire per defaut (%s)', + 'allow_anonymous_refresh' => 'Autorizar l’actualizacion anonime dels fluxes', + 'api_enabled' => 'Autorizar l’accès per API(necessari per las aplicacions mobil)', + 'form' => 'Formulari (tradicional, demanda JavaScript)', + 'http' => 'HTTP (per utilizaires avançats amb HTTPS)', + 'title' => 'Autentificacion', + 'title_reset' => 'Reïnicializacion de l’autentificacion', + 'token' => 'Geton d’autentificacion', + 'token_help' => 'Permetre l’accès a la sortida RSS de l’utilizaire per defaut sens cap d’autentificacion :', + 'type' => 'Mòde d’autentification', + 'unsafe_autologin' => 'Autorizar las connexions automaticas pas seguras al format : ', + ), + 'check_install' => array( + 'cache' => array( + 'nok' => 'Volgatz verificar los dreches sul repertòri ./data/cache. Lo servidor HTTP deu poder escriure dedins', + 'ok' => 'Los dreches sul cache son bons.', + ), + 'categories' => array( + 'nok' => 'La tabla “category” es mala configurada.', + 'ok' => 'La tabla category es corrèctament configurada.', + ), + 'connection' => array( + 'nok' => 'Connexion impossibla a la basa de donadas.', + 'ok' => 'La connexion a la basa de donadas es bona.', + ), + 'ctype' => array( + 'nok' => 'Impossible de trobar una bibliotèca per la verificacion del tipe de caractèrs (php-ctype).', + 'ok' => 'Avètz la bibliotèca per la verificacion del tipe de caractèrs (ctype).', + ), + 'curl' => array( + 'nok' => 'Impossible de trobar la bibliotèca cURL( paquet php-curl).', + 'ok' => 'Avètz la bibliotèca cURL.', + ), + 'data' => array( + 'nok' => 'Volgatz verificar los dreches sul repertòri ./data. Lo servidor HTTP deu poder escriure dedins', + 'ok' => 'Los dreches sul repertòri data son bons.', + ), + 'database' => 'Installacion de la basa de donadas', + 'dom' => array( + 'nok' => 'Impossible de trobar una bibliotèca per percórrer lo DOM (paquet php-xml).', + 'ok' => 'Avètz la bibliotèca per percórrer lo DOM.', + ), + 'entries' => array( + 'nok' => 'La tabla entry es pas configurada coma cal.', + 'ok' => 'La tabla entry es corrèctament configurada.', + ), + 'favicons' => array( + 'nok' => 'Volgatz verificar los dreches sul repertòri ./data/favicons. Lo servidor HTTP deu poder escriure dedins', + 'ok' => 'Los dreches sul repertòri dels favicons son bons.', + ), + 'feeds' => array( + 'nok' => 'La tabla feed es pas configurada coma cal.', + 'ok' => 'La tabla feed es corrèctament configurada.', + ), + 'fileinfo' => array( + 'nok' => 'Avètz pas PHP fileinfo (paquet fileinfo).', + 'ok' => 'Avètz la bibliotèca fileinfo.', + ), + 'files' => 'Installacion dels fichièrs', + 'json' => array( + 'nok' => 'Avètz pas l’extension recomandada JSON (paquet php-json).', + 'ok' => 'Avètz l’exension recomandada JSON.', + ), + 'mbstring' => array( + 'nok' => 'Impossible de trobar la bibliotèca recomandada mbstring per Unicode.', + 'ok' => 'Avètz la bibliotèca recomandada mbstring per Unicode.', + ), + 'minz' => array( + 'nok' => 'Avètz pas la bibliotèca Minz.', + 'ok' => 'Avètz la bibliotèca Minz.', + ), + 'pcre' => array( + 'nok' => 'Impossible de trobar una bibliotèca per las expressions regulara (php-pcre).', + 'ok' => 'Avètz la bibliotèca per las expressions regularas (PCRE).', + ), + 'pdo' => array( + 'nok' => 'Impossible de trobar PDO o un dels drivers compatibles (pdo_mysql, pdo_sqlite, pdo_pgsql).', + 'ok' => 'Avètz PDO e almens un des drivers compatibles (pdo_mysql, pdo_sqlite, pdo_pgsql).', + ), + 'php' => array( + '_' => 'Installacion PHP', + 'nok' => 'Vòstra version PHP es la %s más FreshRSS demanda almens la versión %s.', + 'ok' => 'Vòstra version PHP es %s, qu’es compatibla amb FreshRSS.', + ), + 'tables' => array( + 'nok' => 'Manca una o mai tabla dins la basa de donadas.', + 'ok' => 'Las tablas que cal existisson ben dins la basa de donadas.', + ), + 'title' => 'Verificacion de l’installacion', + 'tokens' => array( + 'nok' => 'Volgatz verificar los dreches sul repertòri ./data/tokens. Lo servidor HTTP deu poder escriure dedins', + 'ok' => 'Los dreches sul repertòri dels getons son bons.', + ), + 'users' => array( + 'nok' => 'Volgatz verificar los dreches sul repertòri ./data/users. Lo servidor HTTP deu poder escriure dedins', + 'ok' => 'Los dreches sul repertòri dels utilizaires son bons.', + ), + 'zip' => array( + 'nok' => 'Avètz pas l’extension ZIP (paquet php-zip).', + 'ok' => 'Avètz l’exension ZIP.', + ), + ), +); diff --git a/app/i18n/oc/conf.php b/app/i18n/oc/conf.php new file mode 100644 index 000000000..7b297805d --- /dev/null +++ b/app/i18n/oc/conf.php @@ -0,0 +1,183 @@ + array( + 'users' => 'Utilizaires', + 'articles_and_size' => '%s articles (%s)', + 'current' => 'Utilizaire actual', + 'is_admin' => 'es administrator', + ), + 'archiving' => array( + '_' => 'Archivar', + 'advanced' => 'Avançat', + 'delete_after' => 'Levar los articles aprèp', + 'help' => 'Mai d’opcions son disponiblas dins la configuracion individuala dels fluxes', + 'keep_history_by_feed' => 'Nombre minimum d’articles de servar per flux', + 'optimize' => 'Optimizar la basa de donada', + 'optimize_help' => 'De far de temps en temps per redusir la talha de la basa de donadas', + 'purge_now' => 'Purgar ara', + 'title' => 'Archivar', + 'ttl' => 'Actualizar pas automaticament mai sovent que', + ), + 'display' => array( + '_' => 'Afichatge', + 'icon' => array( + 'bottom_line' => 'Linha enbàs', + 'entry' => 'Icònas d’article', + 'publication_date' => 'Data de publicacion', + 'related_tags' => 'Etiquetas ligadas', + 'sharing' => 'Partatge', + 'top_line' => 'Linha amont', + ), + 'language' => 'Lenga', + 'notif_html5' => array( + 'seconds' => 'segondas (0 significa cap de timeout)', + 'timeout' => 'Temps d’afichatge de las notificacions HTML5', + ), + 'theme' => 'Tèma', + 'title' => 'Afichatge', + 'width' => array( + 'content' => 'Largor del contengut', + 'large' => 'Larga', + 'medium' => 'Mejana', + 'no_limit' => 'Cap de limit', + 'thin' => 'Fina', + ), + 'show_nav_buttons' => 'Mostrar los botons de navigacion', + ), + 'query' => array( + '_' => 'Filtres utilizaires', + 'deprecated' => 'Aqueste filtre es pas valid. La categoria o lo flux concernit es estat suprimit.', + 'display' => 'Mostrar los resultats del filtre', + 'filter' => 'Filtres aplicats :', + 'get_all' => 'Mostrar totes los articles', + 'get_category' => 'Mostrar la categoria « %s »', + 'get_favorite' => 'Mostrar los articles favorits', + 'get_feed' => 'Mostrar lo flux « %s »', + 'no_filter' => 'Cap de filtre aplicat', + 'none' => 'Avètz pas encara creat cap de filtre.', + 'number' => 'Filtre n°%d', + 'order_asc' => 'Mostrar los articles mai ancians en primièr', + 'order_desc' => 'Mostrar los articles mai recents en primièr', + 'remove' => 'Levar lo filtre utilizaire', + 'search' => 'Recèrca de « %s »', + 'state_0' => 'Mostrar totes los articles', + 'state_1' => 'Mostrar los articles pas legits', + 'state_2' => 'Mostrar los articles pas legits', + 'state_3' => 'Mostrar totes los articles', + 'state_4' => 'Mostrar los articles favorits', + 'state_5' => 'Mostrar los articles legits e en favorits', + 'state_6' => 'Mostrar los articles pas legits e en favorit', + 'state_7' => 'Mostrar los articles favorits', + 'state_8' => 'Mostrar los articles pas en favorit', + 'state_9' => 'Mostrar los articles legits e pas en favorit', + 'state_10' => 'Mostrar los articles pas legits e pas en favorit', + 'state_11' => 'Mostrar los articles pas en favorit', + 'state_12' => 'Mostrar totes los articles', + 'state_13' => 'Mostrar los articles legits', + 'state_14' => 'Mostrar los articles pas legits', + 'state_15' => 'Mostrar totes los articles', + 'title' => 'Filtres utilizaire', + ), + 'profile' => array( + '_' => 'Gestion del perfil', + 'delete' => array( + '_' => 'Supression del compte', + 'warn' => 'Lo compte e totas las donadas ligadas seràn suprimits.', + ), + 'password_api' => 'Senhal API
(ex. : per las aplicacions mobil)', + 'password_form' => 'Senhal API
(ex. : per la connexion via formulari)', + 'password_format' => 'Almens 7 caractèrs', + 'title' => 'Pefil', + ), + 'reading' => array( + '_' => 'Lectura', + 'after_onread' => 'Aprèp « marcar coma legit »,', + 'articles_per_page' => 'Nombre d’articles per pagina', + 'auto_load_more' => 'Cargar los articles seguents enbàs de la pagina', + 'auto_remove_article' => 'Rescondre los articles aprèp lectura', + 'mark_updated_article_unread' => 'Marcar los articles actualizats coma pas legits', + 'confirm_enabled' => 'Mostrar una confirmacion per las accions del tipe « o marcar tot coma legit »', + 'display_articles_unfolded' => 'Mostrar los articles desplegats per defaut', + 'display_categories_unfolded' => 'Mostrar las categorias plegadas per defaut', + 'hide_read_feeds' => 'Rescondre las categorias & fluxes sens articles pas legits (fonciona pas amb la configuracion « Mostrar totes los articles »)', + 'img_with_lazyload' => 'Utilizar lo mòde “cargament tardiu” per las imatges', + 'sides_close_article' => 'Clicar fòra de la zòna de tèxte tampa l’article', + 'jump_next' => 'sautar al vesin venent pas legit (flux o categoria)', + 'number_divided_when_reader' => 'Devisat per 2 dins la vista de lectura.', + 'read' => array( + 'article_open_on_website' => 'quand l’article es dobèrt sul site d’origina', + 'article_viewed' => 'quand l’article es mostrat', + 'scroll' => 'en davalant la pagina', + 'upon_reception' => 'en recebre un article novèl', + 'when' => 'Marcar un article coma legit…', + ), + 'show' => array( + '_' => 'Articles de mostrar', + 'adaptive' => 'Adaptar l’afichatge', + 'all_articles' => 'Mostrar totes los articles', + 'unread' => 'Mostrar pas que los pas legits', + ), + 'sort' => array( + '_' => 'Òrdre de tria', + 'newer_first' => 'Mai recents en primièr', + 'older_first' => 'Mai ancians en primièr', + ), + 'title' => 'Lectura', + 'view' => array( + 'default' => 'Vista per defaut', + 'global' => 'Vista generala', + 'normal' => 'Vista normala', + 'reader' => 'Vista lectura', + ), + 'sticky_post' => 'Gardar l\'article amont quand es dobèrt', + ), + 'sharing' => array( + '_' => 'Partatge', + 'add' => 'Ajustar un metòde de partatge', + 'blogotext' => 'Blogotext', + 'diaspora' => 'Diaspora*', + 'email' => 'Corrièl', + 'facebook' => 'Facebook', + 'g+' => 'Google+', + 'more_information' => 'Mai d’informacions', + 'print' => 'Imprimir', + 'remove' => 'Suprimir lo metòde de partatge', + 'shaarli' => 'Shaarli', + 'share_name' => 'Nom del partatge de mostrar', + 'share_url' => 'URL del partatge d’utilizar', + 'title' => 'Partatge', + 'twitter' => 'Twitter', + 'wallabag' => 'wallabag', + ), + 'shortcut' => array( + '_' => 'Acorchis', + 'auto_share' => 'Partejar', + 'close_dropdown' => 'Tampar los menús', + 'collapse_article' => 'Replegar', + 'first_article' => 'Passar al primièr article', + 'focus_search' => 'Accedir a la recèrca', + 'mark_read' => 'Marcar coma legit', + 'mark_favorite' => 'Ajustar als favorits', + 'navigation' => 'Navigacion', + 'other_action' => 'Autras accions', + 'title' => 'Acorchis', + 'views' => 'Vistas', + 'article_action' => 'Accions ligadas a l\'article', + 'auto_share_help' => 'S’i a pas qu\'un mòde de partatge, aquel serà utilizat. Autrament los mòdes son accessibles per lor numèro.', + 'global_view' => 'Passar a la vista generala', + 'help' => 'Mostrar la documentacion', + 'javascript' => 'Devètz activar lo Javascript per utilizar los acorchis', + 'last_article' => 'Passar al darrièr article', + 'load_more' => 'Cargar mai d’articles', + 'navigation_help' => 'Amb lo modificador «Shift», los acorchis de navigacion s’aplican als fluxes.
Amb lo modificador «Alt», los acorchis de navigacion s’aplican a las categorias.', + 'next_article' => 'Passar a l’article seguent', + 'normal_view' => 'Passar a la vista normala', + 'previous_article' => 'Passar a l’article precedent', + 'reading_view' => 'Passar a la vista lectura', + 'rss_view' => 'Dobrir la vista RSS dins un onglet novèl', + 'see_on_website' => 'Veire al site d’origina', + 'shift_for_all_read' => '+ shift per marcar los articles coma legits', + 'user_filter' => 'Accedir als filtres utilizaire', + 'user_filter_help' => 'S’i a pas qu’un filtre utilizaire, aquel serà utilizat. Autrament los filtres son accessibles per lor numèro.', + ), +); diff --git a/app/i18n/oc/feedback.php b/app/i18n/oc/feedback.php new file mode 100644 index 000000000..225be119b --- /dev/null +++ b/app/i18n/oc/feedback.php @@ -0,0 +1,115 @@ + array( + 'optimization_complete' => 'Optimizacion acabada', + ), + 'access' => array( + 'denied' => 'Avètz pas l’autorizacion d’accedir a aquesta pagina', + 'not_found' => 'La pagina que cercatz existís pas', + ), + 'auth' => array( + 'form' => array( + 'not_set' => 'Un problèma es aparegut pendent la configuracion del sistèma d’autentificacion. Tonatz ensajar ai tard.', + 'set' => 'Lo sistèma d’autentificacion per defaut es ara lo formulari.', + ), + 'login' => array( + 'invalid' => 'L’identificant es invalid', + 'success' => 'Sètz connectat', + ), + 'logout' => array( + 'success' => 'Sètz desconnectat', + ), + 'no_password_set' => 'Pas de senhal es pas configurat. Aquesta foncionalitat es pas disponibla.', + ), + 'conf' => array( + 'error' => 'Una error es apareguda pendent la salvagarda de la configuracion', + 'query_created' => 'Lo filtre « %s » es estat creat.', + 'shortcuts_updated' => 'Los acorchis son actualizats', + 'updated' => 'La configuracion es estada actualizada', + ), + 'extensions' => array( + 'already_enabled' => '%s es ja activada', + 'disable' => array( + 'ko' => '%s pòt pas èsser desactivada. Consultatz los jornals d’audit de FreshRSS logs per mai de detalhs.', + 'ok' => '%s es ara desactivada', + ), + 'enable' => array( + 'ko' => '%s pòt pas èsser activada. Consultatz los jornals d’audit de FreshRSS logs per mai de detalhs.', + 'ok' => '%s es ara activada', + ), + 'no_access' => 'Avètz pas accès sus %s', + 'not_enabled' => '%s es pas encara activada', + 'not_found' => '%s existís pas', + ), + 'import_export' => array( + 'export_no_zip_extension' => 'L\'extension ZIP es pas presenta sul servidor. Volgatz ensajar d\'exportar los fichièrs un per un.', + 'feeds_imported' => 'Vòstres fluxes son estats importats seràn actualizats en seguida', + 'feeds_imported_with_errors' => 'Vòstres fluxes son estats importats mas i a agut d’errors', + 'file_cannot_be_uploaded' => 'Telecargament del fichièr impossible', + 'no_zip_extension' => 'L\'extension es pas presenta sul servidor.', + 'zip_error' => 'Una error s’es producha pendent l’importacion del fichièr ZIP.', + ), + 'sub' => array( + 'actualize' => 'Actualizar', + 'articles' => array( + 'marked_read' => 'Los articles seleccionats son estats marcats coma legits.', + 'marked_unread' => 'Los articles seleccionats son estats marcats coma pas legits.', + ), + 'category' => array( + 'created' => 'La categoria « %s » es estada creada.', + 'deleted' => 'La categoria es estada suprimida.', + 'emptied' => 'La categoria es estada voidada', + 'error' => 'Actualizacion de la categoria impossibla', + 'name_exists' => 'Una categoria se ditz ja atal.', + 'no_id' => 'Vos cal precisar l’id de la categoria.', + 'no_name' => 'Vos cal donar un nom a la categoria.', + 'not_delete_default' => 'Podètz pas suprimir la categoria per defaut !', + 'not_exist' => 'Aquesta categoria existís pas !', + 'over_max' => 'Avètz atengut la limita de categoria (%d)', + 'updated' => 'La categoria es estada actualizada.', + ), + 'feed' => array( + 'actualized' => '%s es a jorn', + 'actualizeds' => 'Los fluxes son estats actualizats', + 'added' => 'Lo flux RSS %s es ajustat', + 'already_subscribed' => 'Seguissètz ja %s', + 'deleted' => 'Lo flux es suprimit', + 'error' => 'Error en actualizar', + 'internal_problem' => 'Lo flux pòt pas èsser ajustat. Consultatz los jornals d’audit de FreshRSS per ne saber mai. Podètz forçar l’apondon en ajustant #force_feed a l’URL.', + 'invalid_url' => 'L\'URL %s es invalida', + 'n_actualized' => '%s fluxes son estats actualizats', + 'n_entries_deleted' => '%d articles son estats suprimits', + 'no_refresh' => 'I a pas cap de flux d’actualizar…', + 'not_added' => '%s a pas pogut èsser ajustat', + 'over_max' => 'Avètz atengut vòstra limita de fluxes (%d)', + 'updated' => 'Lo flux es actualizat', + ), + 'purge_completed' => 'Purga realizada (%s articles suprimits)', + ), + 'update' => array( + 'can_apply' => 'FreshRSS es per èsser mes a jorn en version %s.', + 'error' => 'La mesa a jorn a conegut un problèma : %s', + 'file_is_nok' => 'Nòva version %s disponibla, mas volgatz verificar los dreches sul repertòri %s. Lo servidor HTTP deu poder escriure dedins', + 'finished' => 'Mesa a jorn acabada !', + 'none' => 'Cap de mesa a jorn d’aplicar', + 'server_not_found' => 'Impossible de trobar lo servidor de mesa a jorn. [%s]', + ), + 'user' => array( + 'created' => array( + '_' => 'L’utilizaire %s es estat creat', + 'error' => 'L’utilizaire %s pòt pas èsser creat', + ), + 'deleted' => array( + '_' => 'L’utilizaire %s es estat suprimit', + 'error' => 'L’utilizaire %s pòt pas èsser suprimit', + ), + 'updated' => array( + '_' => 'L’utilizaire %s es estat actualizat', + 'error' => 'L’utilizaire %s es pas estat actualizat', + ), + ), + 'profile' => array( + 'error' => 'Impossible d’actualizar vòstre perfil', + 'updated' => 'Vòstre perfil es estat actualizat', + ), +); diff --git a/app/i18n/oc/gen.php b/app/i18n/oc/gen.php new file mode 100644 index 000000000..cdeb43807 --- /dev/null +++ b/app/i18n/oc/gen.php @@ -0,0 +1,194 @@ + array( + 'cancel' => 'Anullar', + 'disable' => 'Desactivar', + 'enable' => 'Activar', + 'filter' => 'Filtre', + 'import' => 'Importar', + 'submit' => 'Mandar', + 'actualize' => 'Actualizar', + 'back_to_rss_feeds' => '← Tornar a vòstres fluxes RSS', + 'create' => 'Crear', + 'empty' => 'Voidar', + 'export' => 'Exportar', + 'manage' => 'Gerir', + 'mark_favorite' => 'Ajustar als favorits', + 'mark_read' => 'Marcar coma legit', + 'remove' => 'Levar', + 'see_website' => 'Veire lo site', + 'truncate' => 'Suprimir totes los articles', + 'update' => 'Actualizar', + ), + 'auth' => array( + 'email' => 'Adreça de corrièl', + 'logout' => 'Se desconnectar', + 'password' => array( + '_' => 'Senhal', + 'format' => 'Almens 7 caractèrs', + ), + 'username' => array( + '_' => 'Nom d’utilizaire', + 'admin' => 'Nom d’utilizaire administrator', + 'format' => '16 caractèrs alfanumerics maximum)', + ), + 'keep_logged_in' => 'Demorar connectat (%s jorns) ', + 'login' => 'Connexion', + 'registration' => array( + '_' => 'Compte nòu', + 'ask' => 'Crear un compte?', + 'title' => 'Creacion de compte', + ), + 'reset' => 'Reïnicializacion de l’autentificacion', + ), + 'menu' => array( + 'about' => 'A prepaus', + 'admin' => 'Administracion', + 'configuration' => 'Configuracion', + 'user_profile' => 'Perfil', + 'queries' => 'Filtres utilizaire', + 'stats' => 'Estatisticas', + 'archiving' => 'Archivar', + 'authentication' => 'Autentificacion', + 'check_install' => 'Verificacion de l’installacion', + 'display' => 'Afichatge', + 'extensions' => 'Extensions', + 'logs' => 'Jornals d’audit', + 'reading' => 'Lectura', + 'search' => 'Recercar de mots o d’#etiquetas', + 'sharing' => 'Partatge', + 'shortcuts' => 'Acorchis', + 'system' => 'Configuracion sistèma', + 'update' => 'Mesa a jorn', + 'user_management' => 'Gestion dels utilizaires', + ), + 'pagination' => array( + 'next' => 'Seguent', + 'previous' => 'Precedent', + 'first' => 'Debuta', + 'last' => 'Fin', + 'load_more' => 'Cargar mai d’articles', + 'mark_all_read' => 'O marcar tot coma legit', + 'nothing_to_load' => 'I a pas mai d’articles', + ), + 'share' => array( + 'email' => 'Corrièl', + 'mastodon' => 'Mastodon', + 'blogotext' => 'Blogotext', + 'diaspora' => 'Diaspora*', + 'facebook' => 'Facebook', + 'g+' => 'Google+', + 'gnusocial' => 'GNU social', + 'jdh' => 'Journal du hacker', + 'Known' => 'Sites basats sus Known', + 'linkedin' => 'LinkedIn', + 'movim' => 'Movim', + 'pocket' => 'Pocket', + 'print' => 'Imprimir', + 'shaarli' => 'Shaarli', + 'twitter' => 'Twitter', + 'wallabag' => 'wallabag v1', + 'wallabagv2' => 'wallabag v2', + ), + 'short' => array( + 'no' => 'Non', + 'yes' => 'Òc', + 'attention' => 'Atencion !', + 'blank_to_disable' => 'Daissar void per desactivar', + 'by_author' => 'Per %s', + 'by_default' => 'Per defaut', + 'damn' => 'Zut !', + 'default_category' => 'Pas triat', + 'not_applicable' => 'Pas disponible', + 'ok' => 'Òc-ben !', + 'or' => 'o', + ), + 'date' => array( + 'Apr' => 'a\b\r\i\a\l', + 'Aug' => 'a\g\o\s\t', + 'Dec' => '\d\e\c\e\m\b\r\e', + 'Feb' => 'f\e\b\r\i\è\r', + 'Jan' => 'g\e\n\i\è\r', + 'Jul' => 'j\u\l\h\e\t', + 'Jun' => 'j\u\n\h', + 'Mar' => 'm\a\r\ç', + 'May' => '\m\a\i', + 'Nov' => '\n\o\v\e\m\b\r\e', + 'Oct' => '\o\c\t\ò\b\r\e', + 'Sep' => '\s\e\t\e\m\b\r\e', + 'apr' => 'abr.', + 'april' => 'abrial', + 'aug' => 'agost', + 'august' => 'agost', + 'before_yesterday' => 'Abans ièr', + 'dec' => 'dec.', + 'december' => 'decembre', + 'feb' => 'feb.', + 'february' => 'febrièr', + 'format_date' => 'j %s \de\ Y', + 'format_date_hour' => 'j %s \de\ Y \a H\:i', + 'fri' => 'dv', + 'jan' => 'gen.', + 'january' => 'genièr', + 'jul' => 'julh', + 'july' => 'julhet', + 'jun' => 'junh', + 'june' => 'junh', + 'last_3_month' => 'Dempuèi los tres darrièrs meses', + 'last_6_month' => 'Dempuèi los sièis darrièrs meses', + 'last_month' => 'Dempuèi lo mes passat', + 'last_week' => 'Dempuèi la setmana passada', + 'last_year' => 'Dempuèi l’annada passada', + 'mar' => 'març', + 'march' => 'març', + 'may' => 'mai', + 'may_' => 'mai', + 'mon' => 'dl', + 'month' => 'meses', + 'nov' => 'nov.', + 'november' => 'novembre', + 'oct' => 'oct.', + 'october' => 'octòbre', + 'sat' => 'ds', + 'sep' => 'set.', + 'september' => 'setembre', + 'sun' => 'dg', + 'thu' => 'dj', + 'today' => 'Uèi', + 'tue' => 'dm', + 'wed' => 'Dc', + 'yesterday' => 'Ièr', + ), + 'freshrss' => array( + '_' => 'FreshRSS', + 'about' => 'A prepaus de FreshRSS', + ), + 'js' => array( + 'category_empty' => 'Categoria voida', + 'confirm_action' => 'Volètz vertadièrament contunhar ? Aquesta accion se pòt pas anullar !', + 'confirm_action_feed_cat' => 'Volètz vertadièrament contunhar ? Perdretz los favorits e filtres ligats. Aquesta accion se pòt pas anullar !', + 'feedback' => array( + 'body_new_articles' => 'I a %%d nòus articles per legir sus FreshRSS.', + 'request_failed' => 'Una requèsta a fach meuca, aquò pòt venir d’un problèma de connexion Internet.', + 'title_new_articles' => 'FreshRSS : nòus articles !', + ), + 'new_article' => 'I a d’articles nòus disponibles, clicatz per actualizar la página.', + 'should_be_activated' => 'JavaScript deu èsser activat', + ), + 'lang' => array( + 'cz' => 'Čeština', + 'de' => 'Deutsch', + 'en' => 'English', + 'es' => 'Español', + 'fr' => 'Français', + 'he' => 'עברית', + 'it' => 'Italiano', + 'kr' => '한국어', + 'nl' => 'Nederlands', + 'oc' => 'Occitan', + 'pt-br' => 'Português (Brasil)', + 'ru' => 'Русский', + 'tr' => 'Türkçe', + 'zh-cn' => '简体中文', + ), +); diff --git a/app/i18n/oc/index.php b/app/i18n/oc/index.php new file mode 100644 index 000000000..934e19dea --- /dev/null +++ b/app/i18n/oc/index.php @@ -0,0 +1,61 @@ + array( + '_' => 'A prepaus', + 'title' => 'A prepaus', + 'website' => 'Site internet', + 'agpl3' => 'AGPL 3', + 'bugs_reports' => 'Senhalament de problèmas', + 'credits' => 'Crèdits', + 'credits_content' => 'Unes elements de l’estil venon del projècte Bootstrap encara que FreshRSS utilize pas aqueste framework. Lasicònas venon del projècte GNOME. La polissa Open Sans utilizada foguèt creada per en Steve Matteson. FreshRSS es basat sus Minz, un framework PHP.', + 'freshrss_description' => 'FreshRSS es un agregador de fluxes RSS per l’auto-albergar tal coma Kriss Feed o Leed. Sa tòca es d’èsser leugièr e de bon utilizar de prima abòrd mas tanben d’èsser potent e parametrable.', + 'github' => 'on Github', + 'license' => 'Licéncia', + 'project_website' => 'Site del projècte', + 'version' => 'Version', + ), + 'menu' => array( + 'search_short' => 'Recercar', + 'about' => 'A prepaus de FreshRSS', + 'add_query' => 'Crear un filtre', + 'before_one_day' => '1 jorn en arrièr', + 'before_one_week' => '1 setmana en arrièr', + 'favorites' => 'Favorits (%s)', + 'global_view' => 'Vista generala', + 'main_stream' => 'Flux màger', + 'mark_all_read' => 'O marcar tot coma legit', + 'mark_cat_read' => 'Marcar la categoria coma legida', + 'mark_feed_read' => 'Marcar lo flux coma legit', + 'mark_selection_unread' => 'Marcar la seleccion coma pas legida', + 'newer_first' => 'Mai recents en primièr', + 'non-starred' => 'Mostrar los pas favorits', + 'normal_view' => 'Vista normala', + 'older_first' => 'Mai ancians en primièr', + 'queries' => 'Filtres utilizaire', + 'read' => 'Mostrar los legits', + 'reader_view' => 'Vista lectura', + 'rss_view' => 'Flux RSS', + 'starred' => 'Mostrar los favorits', + 'stats' => 'Estatisticas', + 'subscription' => 'Gestion dels abonaments', + 'unread' => 'Mostar los pas legits', + ), + 'share' => 'Partejar', + 'feed' => array( + 'add' => 'Podètz ajustar de fluxes.', + 'empty' => 'I a pas cap de flux de mostrar.', + 'rss_of' => 'Flux RSS de %s', + 'title' => 'Vòstres fluxes RSS', + 'title_global' => 'Vista generala', + 'title_fav' => 'Vòstres favorits', + ), + 'log' => array( + '_' => 'Jornals d’audit', + 'clear' => 'Escafar los jornals', + 'empty' => 'Los jornals son voids', + 'title' => 'Jornals d’audit', + ), + 'tag' => array( + 'related' => 'Etiquetas ligadas', + ), +); diff --git a/app/i18n/oc/install.php b/app/i18n/oc/install.php new file mode 100644 index 000000000..52b56b4ec --- /dev/null +++ b/app/i18n/oc/install.php @@ -0,0 +1,124 @@ + array( + '_' => 'Lenga', + 'choose' => 'Causissètz la lenga per FreshRSS', + 'defined' => 'La lenga es corrèctament definida.', + ), + 'action' => array( + 'finish' => 'Acabar l’installacion', + 'fix_errors_before' => 'Mercés de corregir las errors seguentas abans de contunhar.', + 'keep_install' => 'Gardar la configuracion precedenta', + 'next_step' => 'Anar a l’estapa seguenta', + 'reinstall' => 'Reïnstallar FreshRSS', + ), + 'auth' => array( + 'form' => 'Formulari (tradicional, demanda JavaScript)', + 'http' => 'HTTP (per utilizaires avançats amb HTTPS)', + 'none' => 'Cap (perilhós)', + 'password_form' => 'Senhal API
(ex. : per la connexion via formulari)', + 'password_format' => 'Almens 7 caractèrs', + 'type' => 'Mòde d’autentification', + ), + 'bdd' => array( + '_' => 'Basa de donadas', + 'conf' => array( + '_' => 'Configuracion de la basa de donadas', + 'ko' => 'Verificatz las informacions de la basa de donadas.', + 'ok' => 'La configuracion de la basa de donadas es salvagarda.', + ), + 'host' => 'Òste', + 'prefix' => 'Prefixe de tabla', + 'password' => 'Senhal de la basa de donadas', + 'type' => 'Tipe de basa de donadas', + 'username' => 'Nom d’utilizaire de la basa de donadas', + ), + 'check' => array( + '_' => 'Verificacions', + 'already_installed' => 'Sembla que FreshRSS es ja installat !', + 'cache' => array( + 'nok' => 'Volgatz verificar los dreches sul repertòri ./data/cache. Lo servidor HTTP deu poder escriure dedins', + 'ok' => 'Los dreches sul cache son bons.', + ), + 'ctype' => array( + 'nok' => 'Impossible de trobar una bibliotèca per la verificacion del tipe de caractèrs (php-ctype).', + 'ok' => 'Avètz la bibliotèca per la verificacion del tipe de caractèrs (ctype).', + ), + 'curl' => array( + 'nok' => 'Impossible de trobar la bibliotèca curl ( paquet php-curl).', + 'ok' => 'Avètz la bibliotèca cURL.', + ), + 'data' => array( + 'nok' => 'Volgatz verificar los dreches sul repertòri ./data. Lo servidor HTTP deu poder escriure dedins', + 'ok' => 'Los dreches sul repertòri data son bons.', + ), + 'dom' => array( + 'nok' => 'Impossible de trobar una bibliotèca per percórrer lo DOM.', + 'ok' => 'Avètz la bibliotèca per percórrer lo DOM.', + ), + 'favicons' => array( + 'nok' => 'Volgatz verificar los dreches sul repertòri ./data/favicons. Lo servidor HTTP deu poder escriure dedins', + 'ok' => 'Los dreches sul repertòri dels favicons son bons.', + ), + 'fileinfo' => array( + 'nok' => 'Avètz pas PHP fileinfo (paquet fileinfo).', + 'ok' => 'Avètz la bibliotèca fileinfo.', + ), + 'http_referer' => array( + 'nok' => 'Mercés de verificar que modificatz pas vòstre HTTP REFERER.', + 'ok' => 'Lo HTTP REFERER es conegut e sembla correspondre a vòstre servidor.', + ), + 'json' => array( + 'nok' => 'Impossible de trobar l’extension recomandada JSON (paquet php-json).', + 'ok' => 'Avètz l’exension recomandada JSON.', + ), + 'mbstring' => array( + 'nok' => 'Impossible de trobar la bibliotèca recomandada mbstring per Unicode.', + //TODO + //TODO + 'ok' => 'Avètz la bibliotèca recomandada mbstring per Unicode.', + ), + 'minz' => array( + 'nok' => 'Avètz pas la bibliotèca Minz.', + 'ok' => 'Avètz la bibliotèca Minz.', + ), + 'pcre' => array( + 'nok' => 'Impossible de trobar una bibliotèca per las expressions regulara (php-pcre).', + 'ok' => 'Avètz la bibliotèca per las expressions regularas (PCRE).', + ), + 'pdo' => array( + 'nok' => 'Impossible de trobar PDO o un dels drivers compatibles (pdo_mysql, pdo_sqlite, pdo_pgsql).', + 'ok' => 'Avètz PDO e almens un des drivers compatibles (pdo_mysql, pdo_sqlite, pdo_pgsql).', + ), + 'php' => array( + 'nok' => 'Vòstra version PHP es la %s mas FreshRSS demanda almens la version %s.', + 'ok' => 'Vòstra version PHP es %s, qu’es compatibla amb FreshRSS.', + ), + 'users' => array( + 'nok' => 'Volgatz verificar los dreches sul repertòri ./data/users. Lo servidor HTTP deu poder escriure dedins', + 'ok' => 'Los dreches sul repertòri dels utilizaires son bons.', + ), + 'xml' => array( + 'nok' => 'Impossible de trobar una bibliotèca necessària per XML.', + 'ok' => 'Avètz la bibliotèca per percórrer los XML.', + ), + ), + 'conf' => array( + '_' => 'Configuracion generala', + 'ok' => 'La configuracion generala es enregistrada.', + ), + 'congratulations' => 'Òsca !', + 'default_user' => 'Nom d’utilizaire per defaut 16 caractèrs alfanumerics maximum)', + 'delete_articles_after' => 'Levar los articles aprèp', + 'fix_errors_before' => 'Mercés de corregir las errors seguentas abans de contunhar.', + 'javascript_is_better' => 'FreshRSS es mai agradable amb lo JavaScript activat', + 'js' => array( + 'confirm_reinstall' => 'En reïnstallant FreshRSS perdretz la configuracion precedenta. Volètz vertadièrament contunhar ?', + ), + 'not_deleted' => 'Quicòm a trucat, sembla qu’avètz suprimit %s a la man.', + 'ok' => 'L’installacion s’es corrèctament passada.', + 'step' => 'etapa %d', + 'steps' => 'Etapas', + 'title' => 'Installacion · FreshRSS', + 'this_is_the_end' => 'Es la fin', +); diff --git a/app/i18n/oc/sub.php b/app/i18n/oc/sub.php new file mode 100644 index 000000000..6b4bfd072 --- /dev/null +++ b/app/i18n/oc/sub.php @@ -0,0 +1,84 @@ + array( + 'title' => 'API', + 'documentation' => 'Copiar l’URL seguenta per l’utilizaire dins d’una aisina extèrna.', + ), + 'bookmarklet' => array( + 'label' => 'S’abonar', + 'documentation' => 'Depausatz aqueste boton per la barra de marcapaginas o clicatz-lo a drecha e causissètz « Enregistrar aqueste ligam». Puèi clicatz «S’abonar» sus las paginas que volètz seguir.', + 'title' => 'Bookmarklet', + ), + 'category' => array( + 'new' => 'Nòva categoria', + '_' => 'Categoria', + 'add' => 'Ajustar una categoria', + 'empty' => 'Categoria voida', + ), + 'feed' => array( + 'auth' => array( + 'password' => 'Senhal HTTP', + 'username' => 'Identificant HTTP', + 'configuration' => 'Identificacion', + 'help' => 'Permet l’accès als fluxes protegits per una autentificacion HTTP', + 'http' => 'Autentificacion HTTP', + ), + 'description' => 'Descripcion', + 'mute' => 'mut', + 'priority' => array( + '_' => 'Visibilitat', + 'archived' => 'Mostrar pas (archivat)', + 'main_stream' => 'Mostar al flux màger', + 'normal' => 'Mostar dins sa categoria', + ), + 'title' => 'Títol', + 'url' => 'Flux URL', + 'website' => 'URL del site', + 'add' => 'Ajustar un flux RSS', + 'advanced' => 'Avançat', + 'archiving' => 'Archivar', + 'css_path' => 'Selector CSS dels articles sul site d’origina', + 'empty' => 'Aqueste flux es void. Assegurats-vos qu’es totjorn mantengut.', + 'informations' => 'Informacions', + 'keep_history' => 'Nombre minimum d’articles de servar', + 'no_selected' => 'Cap de flux pas seleccionat.', + 'number_entries' => '%d articles', + 'ssl_verify' => 'Verificacion de la seguretat SSL', + 'stats' => 'Estatisticas', + 'think_to_add' => 'Podètz ajustar de fluxes.', + 'timeout' => 'Temps d’espèra en segondas', + 'title_add' => 'Ajustar un flux RSS', + 'ttl' => 'Actualizar pas automaticament mai sovent que', + 'validator' => 'Verificar la validitat del flux', + 'pubsubhubbub' => 'Notificaciones instantáneas amb PubSubHubbub', + 'css_help' => 'Permet de recuperar los fluxes troncats (atencion, demanda mai de temps !)', + 'error' => 'Aqueste flux a rescontrat un problèma. Volgatz verificar que siá totjorn accessible puèi actualizatz-lo.', + 'moved_category_deleted' => 'Quand escafatz una categoria, sos fluxes son automaticament classats dins %s.', + ), + 'import_export' => array( + 'export' => 'Exportar', + 'export_starred' => 'Exportar los favorits', + 'import' => 'Importar', + 'title' => 'Importar / Exportar', + 'export_opml' => 'Exportar la lista de fluxes (OPML)', + 'feed_list' => 'Lista dels %s articles', + 'file_to_import' => 'Fichièr d’importar
(OPML, JSON o ZIP)', + 'file_to_import_no_zip' => 'Fichièr d’importar
(OPML o JSON)', + 'starred_list' => 'Lista dels articles favorits', + ), + 'menu' => array( + 'import_export' => 'Importar / Exportar', + 'subscription_tools' => 'Aisinas d’abonament', + 'bookmark' => 'Sabonar (marcapagina FreshRSS)', + 'subscription_management' => 'Gestion dels abonaments', + ), + 'title' => array( + 'subscription_tools' => 'Aisinas d’abonament', + '_' => 'Gestión dels abonaments', + 'feed_management' => 'Gestion dels fluxes RSS', + ), + 'firefox' => array( + 'title' => 'Lector de flux de Firefox', + 'documentation' => 'Seguir las etapas descrichas aquí per ajustar FreshRSS a la lista dels lectors de flux de Firefox.', + ), +); diff --git a/app/i18n/pt-br/gen.php b/app/i18n/pt-br/gen.php index 59218597b..bf5214ee8 100644 --- a/app/i18n/pt-br/gen.php +++ b/app/i18n/pt-br/gen.php @@ -123,6 +123,7 @@ return array( 'it' => 'Italiano', 'kr' => '한국어', 'nl' => 'Nederlands', + 'oc' => 'Occitan', 'pt-br' => 'Português (Brasil)', 'ru' => 'Русский', 'tr' => 'Türkçe', diff --git a/app/i18n/ru/gen.php b/app/i18n/ru/gen.php index 6c8dd2adf..f2612793a 100644 --- a/app/i18n/ru/gen.php +++ b/app/i18n/ru/gen.php @@ -124,6 +124,7 @@ return array( 'it' => 'Italiano', 'kr' => '한국어', 'nl' => 'Nederlands', + 'oc' => 'Occitan', 'pt-br' => 'Português (Brasil)', 'ru' => 'Русский', 'tr' => 'Türkçe', diff --git a/app/i18n/tr/gen.php b/app/i18n/tr/gen.php index b8dc18c01..7328fb380 100644 --- a/app/i18n/tr/gen.php +++ b/app/i18n/tr/gen.php @@ -124,6 +124,7 @@ return array( 'it' => 'Italiano', 'kr' => '한국어', 'nl' => 'Nederlands', + 'oc' => 'Occitan', 'pt-br' => 'Português (Brasil)', 'ru' => 'Русский', 'tr' => 'Türkçe', diff --git a/app/i18n/zh-cn/gen.php b/app/i18n/zh-cn/gen.php index 078e1d378..c68396e5d 100644 --- a/app/i18n/zh-cn/gen.php +++ b/app/i18n/zh-cn/gen.php @@ -124,6 +124,7 @@ return array( 'it' => 'Italiano', 'kr' => '한국어', 'nl' => 'Nederlands', + 'oc' => 'Occitan', 'pt-br' => 'Português (Brasil)', 'ru' => 'Русский', 'tr' => 'Türkçe', diff --git a/cli/i18n/ignore/oc.php b/cli/i18n/ignore/oc.php new file mode 100644 index 000000000..6413fc5f0 --- /dev/null +++ b/cli/i18n/ignore/oc.php @@ -0,0 +1,56 @@ + Date: Sun, 4 Nov 2018 15:08:16 +0100 Subject: Missing string (#2114) --- app/i18n/oc/index.php | 1 + 1 file changed, 1 insertion(+) (limited to 'app') diff --git a/app/i18n/oc/index.php b/app/i18n/oc/index.php index 934e19dea..b357a48e2 100644 --- a/app/i18n/oc/index.php +++ b/app/i18n/oc/index.php @@ -38,6 +38,7 @@ return array( 'starred' => 'Mostrar los favorits', 'stats' => 'Estatisticas', 'subscription' => 'Gestion dels abonaments', + 'tags' => 'Mas etiquetas', 'unread' => 'Mostar los pas legits', ), 'share' => 'Partejar', -- cgit v1.2.3 From cf899d8d25c57b05dff89b89e2c7e56808f83c50 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Mon, 5 Nov 2018 18:10:38 +0100 Subject: TT-RSS import (#2099) * TT-RSS import Import of Tiny Tiny RSS favourites https://github.com/FreshRSS/FreshRSS/issues/2018#issuecomment-432710462 * Fallback feed_url * Simpler JSON * TT-RSS import custom labels * Fix syntax --- app/Controllers/importExportController.php | 162 ++++++++++++++++++++++++----- app/Models/EntryDAO.php | 9 ++ app/Models/FeedDAO.php | 8 +- app/Models/TagDAO.php | 2 +- app/views/helpers/export/articles.phtml | 18 +++- 5 files changed, 171 insertions(+), 28 deletions(-) (limited to 'app') diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php index 0fb5ba651..c7b384540 100644 --- a/app/Controllers/importExportController.php +++ b/app/Controllers/importExportController.php @@ -109,6 +109,17 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { } } } + foreach ($list_files['ttrss_starred'] as $article_file) { + $json = $this->ttrssXmlToJson($article_file); + if (!$this->importJson($json, true)) { + $ok = false; + if (FreshRSS_Context::$isCli) { + fwrite(STDERR, 'FreshRSS error during TT-RSS articles import' . "\n"); + } else { + Minz_Log::warning('Error during TT-RSS articles import'); + } + } + } return $ok; } @@ -165,17 +176,22 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { private static function guessFileType($filename) { if (substr_compare($filename, '.zip', -4) === 0) { return 'zip'; - } elseif (substr_compare($filename, '.opml', -5) === 0 || - substr_compare($filename, '.xml', -4) === 0) { + } elseif (substr_compare($filename, '.opml', -5) === 0) { return 'opml'; - } elseif (substr_compare($filename, '.json', -5) === 0 && - strpos($filename, 'starred') !== false) { - return 'json_starred'; } elseif (substr_compare($filename, '.json', -5) === 0) { - return 'json_feed'; - } else { - return 'unknown'; + if (strpos($filename, 'starred') !== false) { + return 'json_starred'; + } else { + return 'json_feed'; + } + } elseif (substr_compare($filename, '.xml', -4) === 0) { + if (preg_match('/Tiny|tt-?rss/i', $filename)) { + return 'ttrss_starred'; + } else { + return 'opml'; + } } + return 'unknown'; } /** @@ -364,6 +380,43 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { return !$error; } + private function ttrssXmlToJson($xml) { + $table = (array)simplexml_load_string($xml, null, LIBXML_NOCDATA); + $table['items'] = isset($table['article']) ? $table['article'] : array(); + unset($table['article']); + for ($i = count($table['items']) - 1; $i >= 0; $i--) { + $item = (array)($table['items'][$i]); + $item['updated'] = isset($item['updated']) ? strtotime($item['updated']) : ''; + $item['published'] = $item['updated']; + $item['content'] = array('content' => isset($item['content']) ? $item['content'] : ''); + $item['categories'] = isset($item['tag_cache']) ? array($item['tag_cache']) : array(); + if (!empty($item['marked'])) { + $item['categories'][] = 'user/-/state/com.google/starred'; + } + if (!empty($item['published'])) { + $item['categories'][] = 'user/-/state/com.google/broadcast'; + } + if (!empty($item['label_cache'])) { + $labels_cache = json_decode($item['label_cache'], true); + if (is_array($labels_cache)) { + foreach ($labels_cache as $label_cache) { + if (!empty($label_cache[1])) { + $item['categories'][] = 'user/-/label/' . trim($label_cache[1]); + } + } + } + } + $item['alternate'][0]['href'] = isset($item['link']) ? $item['link'] : ''; + $item['origin'] = array( + 'title' => isset($item['feed_title']) ? $item['feed_title'] : '', + 'feedUrl' => isset($item['feed_url']) ? $item['feed_url'] : '', + ); + $item['id'] = isset($item['guid']) ? $item['guid'] : (isset($item['feed_url']) ? $item['feed_url'] : $item['published']); + $table['items'][$i] = $item; + } + return json_encode($table); + } + /** * This method import a JSON-based file (Google Reader format). * @@ -405,7 +458,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { // Oops, no more place! Minz_Log::warning(_t('feedback.sub.feed.over_max', $limits['max_feeds'])); } else { - $feed = $this->addFeedJson($item['origin'], $google_compliant); + $feed = $this->addFeedJson($item['origin']); } if ($feed == null) { @@ -425,6 +478,15 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { } } + $tagDAO = FreshRSS_Factory::createTagDao(); + $labels = $tagDAO->listTags(); + $knownLabels = array(); + foreach ($labels as $label) { + $knownLabels[$label->name()]['id'] = $label->id(); + $knownLabels[$label->name()]['articles'] = array(); + } + unset($labels); + // For each feed, check existing GUIDs already in database. $existingHashForGuids = array(); foreach ($newFeedGuids as $feedId => $newGuids) { @@ -443,19 +505,36 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $feed_id = $article_to_feed[$item['id']]; $author = isset($item['author']) ? $item['author'] : ''; - $key_content = ($google_compliant && !isset($item['content'])) ? 'summary' : 'content'; + $is_starred = $starred; $tags = $item['categories']; - if ($google_compliant) { - // Remove tags containing "/state/com.google" which are useless. - $tags = array_filter($tags, function($var) { - return strpos($var, '/state/com.google') !== false; - }); + $labels = array(); + for ($i = count($tags) - 1; $i >= 0; $i --) { + $tag = trim($tags[$i]); + if (strpos($tag, 'user/-/') !== false) { + if ($tag === 'user/-/state/com.google/starred') { + $is_starred = true; + } elseif (strpos($tag, 'user/-/label/') === 0) { + $tag = trim(substr($tag, 13)); + if ($tag != '') { + $labels[] = $tag; + } + } + unset($tags[$i]); + } + } + + $url = $item['alternate'][0]['href']; + if (!empty($item['content']['content'])) { + $content = $item['content']['content']; + } elseif (!empty($item['summary']['content'])) { + $content = $item['summary']['content']; } + $content = sanitizeHTML($content, $url); $entry = new FreshRSS_Entry( $feed_id, $item['id'], $item['title'], $author, - $item[$key_content]['content'], $item['alternate'][0]['href'], - $item['published'], $is_read, $starred + $content, $url, + $item['published'], $is_read, $is_starred ); $entry->_id(min(time(), $entry->date(true)) . uSecString()); $entry->_tags($tags); @@ -478,8 +557,21 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { } else { $ok = $this->entryDAO->addEntry($values); } - $error |= ($ok === false); + foreach ($labels as $labelName) { + if (empty($knownLabels[$labelName]['id'])) { + $labelId = $tagDAO->addTag(array('name' => $labelName)); + $knownLabels[$labelName]['id'] = $labelId; + $knownLabels[$labelName]['articles'] = array(); + } + $knownLabels[$labelName]['articles'][] = array( + //'id' => $entry->id(), //ID changes after commitNewEntries() + 'id_feed' => $entry->feed(), + 'guid' => $entry->guid(), + ); + } + + $error |= ($ok === false); } $this->entryDAO->commit(); @@ -488,6 +580,20 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $this->feedDAO->updateCachedValues(); $this->entryDAO->commit(); + $this->entryDAO->beginTransaction(); + foreach ($knownLabels as $labelName => $knownLabel) { + $labelId = $knownLabel['id']; + foreach ($knownLabel['articles'] as $article) { + $entryId = $this->entryDAO->searchIdByGuid($article['id_feed'], $article['guid']); + if ($entryId != null) { + $tagDAO->tagEntry($labelId, $entryId); + } else { + Minz_Log::warning('Could not add label "' . $labelName . '" to entry "' . $article['guid'] . '" in feed ' . $article['id_feed']); + } + } + } + $this->entryDAO->commit(); + return !$error; } @@ -495,16 +601,24 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { * This method import a JSON-based feed (Google Reader format). * * @param array $origin represents a feed. - * @param boolean $google_compliant takes care of some specific values if true. * @return FreshRSS_Feed if feed is in database at the end of the process, * else null. */ - private function addFeedJson($origin, $google_compliant) { + private function addFeedJson($origin) { $return = null; - $key = $google_compliant ? 'htmlUrl' : 'feedUrl'; - $url = $origin[$key]; - $name = $origin['title']; - $website = $origin['htmlUrl']; + if (!empty($origin['feedUrl'])) { + $url = $origin['feedUrl']; + } elseif (!empty($origin['htmlUrl'])) { + $url = $origin['htmlUrl']; + } else { + return null; + } + if (!empty($origin['htmlUrl'])) { + $website = $origin['htmlUrl']; + } elseif (!empty($origin['feedUrl'])) { + $website = $origin['feedUrl']; + } + $name = empty($origin['title']) ? '' : $origin['title']; try { // Create a Feed object and add it in database. diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index a01c2227b..708d01a69 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -671,6 +671,15 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { return isset($entries[0]) ? $entries[0] : null; } + public function searchIdByGuid($id_feed, $guid) { + $sql = 'SELECT id FROM `' . $this->prefix . 'entry` WHERE id_feed=? AND guid=?'; + $stm = $this->bd->prepare($sql); + $values = array($id_feed, $guid); + $stm->execute($values); + $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0); + return isset($res[0]) ? $res[0] : null; + } + protected function sqlConcat($s1, $s2) { return 'CONCAT(' . $s1 . ',' . $s2 . ')'; //MySQL } diff --git a/app/Models/FeedDAO.php b/app/Models/FeedDAO.php index e579f5881..7f00642f4 100644 --- a/app/Models/FeedDAO.php +++ b/app/Models/FeedDAO.php @@ -465,9 +465,15 @@ UPDATE `{$this->prefix}feed` SQL; $stm = $this->bd->prepare($sql); if (!($stm && $stm->execute(array(':new_value' => FreshRSS_Feed::TTL_DEFAULT, ':old_value' => -2)))) { + $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + Minz_Log::error('SQL warning updateTTL 1: ' . $info[2] . ' ' . $sql); + $sql2 = 'ALTER TABLE `' . $this->prefix . 'feed` ADD COLUMN ttl INT NOT NULL DEFAULT ' . FreshRSS_Feed::TTL_DEFAULT; //v0.7.3 $stm = $this->bd->prepare($sql2); - $stm->execute(); + if (!($stm && $stm->execute())) { + $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + Minz_Log::error('SQL error updateTTL 2: ' . $info[2] . ' ' . $sql2); + } } else { $stm->execute(array(':new_value' => -3600, ':old_value' => -1)); } diff --git a/app/Models/TagDAO.php b/app/Models/TagDAO.php index 1b59c8971..b55d2b35d 100644 --- a/app/Models/TagDAO.php +++ b/app/Models/TagDAO.php @@ -266,7 +266,7 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable { if (is_array($entries) && count($entries) > 0) { $sql .= ' AND et.id_entry IN (' . str_repeat('?,', count($entries) - 1). '?)'; foreach ($entries as $entry) { - $values[] = $entry->id(); + $values[] = is_array($entry) ? $entry['id'] : $entry->id(); } } $stm = $this->bd->prepare($sql); diff --git a/app/views/helpers/export/articles.phtml b/app/views/helpers/export/articles.phtml index b8958f527..59a2c7ad7 100644 --- a/app/views/helpers/export/articles.phtml +++ b/app/views/helpers/export/articles.phtml @@ -16,6 +16,12 @@ $articles = array( echo rtrim(json_encode($articles, $options), " ]}\n\r\t"), "\n"; $first = true; +$tagDAO = FreshRSS_Factory::createTagDao(); +$entryIdsTagNames = $tagDAO->getEntryIdsTagNames($this->entriesRaw); +if ($entryIdsTagNames == false) { + $entryIdsTagNames = array(); +} + foreach ($this->entriesRaw as $entryRaw) { if (empty($entryRaw)) { continue; @@ -32,13 +38,14 @@ foreach ($this->entriesRaw as $entryRaw) { $article = array( 'id' => $entry->guid(), + 'timestampUsec' => '' . $entry->id(), 'categories' => array_values($entry->tags()), 'title' => $entry->title(), - 'author' => $entry->authors(true), //TODO: Make an array like tags? + 'author' => $entry->authors(true), 'published' => $entry->date(true), 'updated' => $entry->date(true), 'alternate' => array(array( - 'href' => $entry->link(), + 'href' => htmlspecialchars_decode($entry->link(), ENT_QUOTES), 'type' => 'text/html', )), 'content' => array( @@ -51,6 +58,13 @@ foreach ($this->entriesRaw as $entryRaw) { 'feedUrl' => $feed == null ? '' : $feed->url(), ) ); + if ($entry->isFavorite()) { + $article['categories'][] = 'user/-/state/com.google/starred'; + } + $tagNames = isset($entryIdsTagNames['e_' . $entry->id()]) ? $entryIdsTagNames['e_' . $entry->id()] : array(); + foreach ($tagNames as $tagName) { + $article['categories'][] = 'user/-/label/' . $tagName; + } $line = json_encode($article, $options); if ($line != '') { -- cgit v1.2.3 From b672fc190d7df163449e91400c6d6a08a3775835 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sun, 11 Nov 2018 17:31:50 +0100 Subject: Tweaks for Vienna RSS (#2093) * Tweaks for Vienna RSS https://github.com/FreshRSS/FreshRSS/issues/2091 https://github.com/ViennaRSS/vienna-rss/issues/1197 * Fix get feed by URL * Fix get item ids returning starred elements * API add item ids by feed URL * Add API filter `it` https://feedhq.readthedocs.io/en/latest/api/reference.html#stream-items-ids * API add `nt=` filter + refactoring * No ; prefix for author https://github.com/FreshRSS/FreshRSS/issues/2091#issuecomment-435562495 * Add id long form prefix and accept short id form https://github.com/FreshRSS/FreshRSS/issues/2091#issuecomment-435631259 * Fix quote problem https://github.com/FreshRSS/FreshRSS/issues/2091#issuecomment-435683930 * Isolate bug fix for News+ https://github.com/FreshRSS/FreshRSS/issues/2091#issuecomment-435687041 * Rework encoding conventions https://github.com/FreshRSS/FreshRSS/issues/2091#issuecomment-437441834 * Unicode escaping alternative Alternative approach to encode XML special characters and other problematic characters into their Unicode fullwidth version when we cannot use HTML-encoding because clients disagree wether they should HTML-decode or not. https://github.com/FreshRSS/FreshRSS/issues/2091#issuecomment-436059559 --- app/Models/BooleanSearch.php | 8 ++ app/Models/EntryDAO.php | 4 +- app/Models/Feed.php | 2 +- app/Models/Search.php | 8 ++ lib/lib_rss.php | 12 +++ p/api/greader.php | 226 +++++++++++++++++++++++++++---------------- 6 files changed, 171 insertions(+), 89 deletions(-) (limited to 'app') diff --git a/app/Models/BooleanSearch.php b/app/Models/BooleanSearch.php index 6e016f7e9..88eeea73c 100644 --- a/app/Models/BooleanSearch.php +++ b/app/Models/BooleanSearch.php @@ -45,6 +45,14 @@ class FreshRSS_BooleanSearch { return $this->searches; } + public function add($search) { + if ($search instanceof FreshRSS_Search) { + $this->searches[] = $search; + return $search; + } + return null; + } + public function __toString() { return $this->getRawInput(); } diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index 708d01a69..6d77a33cd 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -921,8 +921,8 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { return self::daoToEntries($stm->fetchAll(PDO::FETCH_ASSOC)); } - public function listIdsWhere($type = 'a', $id = '', $state = FreshRSS_Entry::STATE_ALL, $order = 'DESC', $limit = 1, $firstId = '', $filters = null, $date_min = 0) { //For API - list($values, $sql) = $this->sqlListWhere($type, $id, $state, $order, $limit, $firstId, $filters, $date_min); + public function listIdsWhere($type = 'a', $id = '', $state = FreshRSS_Entry::STATE_ALL, $order = 'DESC', $limit = 1, $firstId = '', $filters = null) { //For API + list($values, $sql) = $this->sqlListWhere($type, $id, $state, $order, $limit, $firstId, $filters); $stm = $this->bd->prepare($sql); $stm->execute($values); diff --git a/app/Models/Feed.php b/app/Models/Feed.php index e1dd2990d..a5ef33d6b 100644 --- a/app/Models/Feed.php +++ b/app/Models/Feed.php @@ -424,7 +424,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 .= escapeToUnicodeAlternative(strip_tags($author->name == '' ? $author->email : $author->name)) . '; '; } } $author_names = substr($author_names, 0, -2); diff --git a/app/Models/Search.php b/app/Models/Search.php index c52e391fa..f9cda7354 100644 --- a/app/Models/Search.php +++ b/app/Models/Search.php @@ -73,10 +73,18 @@ class FreshRSS_Search { return $this->min_date; } + public function setMinDate($value) { + return $this->min_date = $value; + } + public function getMaxDate() { return $this->max_date; } + public function setMaxDate($value) { + return $this->max_date = $value; + } + public function getMinPubdate() { return $this->min_pubdate; } diff --git a/lib/lib_rss.php b/lib/lib_rss.php index 4087f6faf..52e4408d2 100644 --- a/lib/lib_rss.php +++ b/lib/lib_rss.php @@ -102,6 +102,18 @@ function safe_ascii($text) { return filter_var($text, FILTER_DEFAULT, FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH); } +function escapeToUnicodeAlternative($text) { + $text = htmlspecialchars_decode($text, ENT_QUOTES); + // https://raw.githubusercontent.com/mihaip/google-reader-api/master/wiki/StreamId.wiki + return trim(str_replace( + //Problematic characters + array("'", '"', '^', '<', '>', '?', '&', '\\', '/', ',', ';'), + //Use their fullwidth Unicode form instead: + array("’", '"', '^', '<', '>', '?', '&', '\', '/', ',', ';'), + $text + )); +} + /** * Test if a given server address is publicly accessible. * diff --git a/p/api/greader.php b/p/api/greader.php index c6701096c..7c5c54951 100644 --- a/p/api/greader.php +++ b/p/api/greader.php @@ -19,6 +19,7 @@ Server-side API compatible with Google Reader API layer 2 * https://github.com/devongovett/reader * https://github.com/theoldreader/api * https://www.inoreader.com/developers/ +* https://feedhq.readthedocs.io/en/latest/api/index.html */ require(__DIR__ . '/../../constants.php'); @@ -198,6 +199,7 @@ function clientLogin($email, $pass) { //http://web.archive.org/web/2013060409104 header('Content-Type: text/plain; charset=UTF-8'); $auth = $email . '/' . sha1(FreshRSS_Context::$system_conf->salt . $email . FreshRSS_Context::$user_conf->apiPasswordHash); echo 'SID=', $auth, "\n", + 'LSID=null', "\n", //Vienna RSS 'Auth=', $auth, "\n"; exit(); } else { @@ -258,7 +260,7 @@ function tagList() { foreach ($res as $cName) { $tags[] = array( - 'id' => 'user/-/label/' . $cName, + 'id' => 'user/-/label/' . htmlspecialchars_decode($cName, ENT_QUOTES), //'sortid' => $cName, 'type' => 'folder', //Inoreader ); @@ -270,7 +272,7 @@ function tagList() { $labels = $tagDAO->listTags(true); foreach ($labels as $label) { $tags[] = array( - 'id' => 'user/-/label/' . $label->name(), + 'id' => 'user/-/label/' . htmlspecialchars_decode($label->name(), ENT_QUOTES), //'sortid' => $cName, 'type' => 'tag', //Inoreader 'unread_count' => $label->nbUnread(), //Inoreader @@ -298,17 +300,17 @@ function subscriptionList() { foreach ($res as $line) { $subscriptions[] = array( 'id' => 'feed/' . $line['id'], - 'title' => $line['name'], + 'title' => escapeToUnicodeAlternative($line['name']), 'categories' => array( array( - 'id' => 'user/-/label/' . $line['c_name'], - 'label' => $line['c_name'], + 'id' => 'user/-/label/' . htmlspecialchars_decode($line['c_name'], ENT_QUOTES), + 'label' => htmlspecialchars_decode($line['c_name'], ENT_QUOTES), ), ), //'sortid' => $line['name'], //'firstitemmsec' => 0, - 'url' => $line['url'], - 'htmlUrl' => $line['website'], + 'url' => htmlspecialchars_decode($line['url'], ENT_QUOTES), + 'htmlUrl' => htmlspecialchars_decode($line['website'], ENT_QUOTES), 'iconUrl' => $faviconsUrl . hash('crc32b', $salt . $line['url']), ); } @@ -345,6 +347,7 @@ function subscriptionEdit($streamNames, $titles, $action, $add = '', $remove = ' $c_name = ''; } } + $c_name = htmlspecialchars($c_name, ENT_COMPAT, 'UTF-8'); $cat = $categoryDAO->searchByName($c_name); $addCatId = $cat == null ? 0 : $cat->id(); } else if ($remove != '' && strpos($remove, 'user/-/label/')) { @@ -355,26 +358,28 @@ function subscriptionEdit($streamNames, $titles, $action, $add = '', $remove = ' badRequest(); } for ($i = count($streamNames) - 1; $i >= 0; $i--) { - $streamName = $streamNames[$i]; //feed/http://example.net/sample.xml ; feed/338 - if (strpos($streamName, 'feed/') === 0) { - $streamName = substr($streamName, 5); + $streamUrl = $streamNames[$i]; //feed/http://example.net/sample.xml ; feed/338 + if (strpos($streamUrl, 'feed/') === 0) { + $streamUrl = substr($streamUrl, 5); $feedId = 0; - if (ctype_digit($streamName)) { + if (ctype_digit($streamUrl)) { if ($action === 'subscribe') { continue; } - $feedId = $streamName; + $feedId = $streamUrl; } else { - $feed = $feedDAO->searchByUrl($streamName); + $streamUrl = htmlspecialchars($streamUrl, ENT_COMPAT, 'UTF-8'); + $feed = $feedDAO->searchByUrl($streamUrl); $feedId = $feed == null ? -1 : $feed->id(); } $title = isset($titles[$i]) ? $titles[$i] : ''; + $title = htmlspecialchars($title, ENT_COMPAT, 'UTF-8'); switch ($action) { case 'subscribe': if ($feedId <= 0) { - $http_auth = ''; //TODO + $http_auth = ''; try { - $feed = FreshRSS_feed_Controller::addFeed($streamName, $title, $addCatId, $c_name, $http_auth); + $feed = FreshRSS_feed_Controller::addFeed($streamUrl, $title, $addCatId, $c_name, $http_auth); continue; } catch (Exception $e) { Minz_Log::error('subscriptionEdit error subscribe: ' . $e->getMessage(), API_LOG); @@ -407,6 +412,7 @@ function subscriptionEdit($streamNames, $titles, $action, $add = '', $remove = ' function quickadd($url) { try { + $url = htmlspecialchars($url, ENT_COMPAT, 'UTF-8'); $feed = FreshRSS_feed_Controller::addFeed($url); exit(json_encode(array( 'numResults' => 1, @@ -442,7 +448,7 @@ function unreadCount() { //http://blog.martindoms.com/2009/10/16/using-the-googl } } $unreadcounts[] = array( - 'id' => 'user/-/label/' . $cat->name(), + 'id' => 'user/-/label/' . htmlspecialchars_decode($cat->name(), ENT_QUOTES), 'count' => $cat->nbNotRead(), 'newestItemTimestampUsec' => $catLastUpdate . '000000', ); @@ -455,7 +461,7 @@ function unreadCount() { //http://blog.martindoms.com/2009/10/16/using-the-googl $tagDAO = FreshRSS_Factory::createTagDao(); foreach ($tagDAO->listTags(true) as $label) { $unreadcounts[] = array( - 'id' => 'user/-/label/' . $label->name(), + 'id' => 'user/-/label/' . htmlspecialchars_decode($label->name(), ENT_QUOTES), 'count' => $label->nbUnread(), ); } @@ -496,28 +502,29 @@ function entriesToArray($entries) { $f_name = '_'; } $item = array( - 'id' => /*'tag:google.com,2005:reader/item/' .*/ dec2hex($entry->id()), //64-bit hexa http://code.google.com/p/google-reader-api/wiki/ItemId + 'id' => 'tag:google.com,2005:reader/item/' . dec2hex($entry->id()), //64-bit hexa http://code.google.com/p/google-reader-api/wiki/ItemId 'crawlTimeMsec' => substr($entry->id(), 0, -3), 'timestampUsec' => '' . $entry->id(), //EasyRSS 'published' => $entry->date(true), - 'title' => $entry->title(), + 'title' => escapeToUnicodeAlternative($entry->title()), 'summary' => array('content' => $entry->content()), 'alternate' => array( array('href' => htmlspecialchars_decode($entry->link(), ENT_QUOTES)), ), 'categories' => array( 'user/-/state/com.google/reading-list', - 'user/-/label/' . $c_name, + 'user/-/label/' . htmlspecialchars_decode($c_name, ENT_QUOTES), ), 'origin' => array( 'streamId' => 'feed/' . $f_id, - 'title' => $f_name, //EasyRSS + 'title' => escapeToUnicodeAlternative($f_name), //EasyRSS //'htmlUrl' => $line['f_website'], ), ); $author = $entry->authors(true); + $author = trim($author, '; '); if ($author != '') { - $item['author'] = $author; + $item['author'] = escapeToUnicodeAlternative($author); } if ($entry->isRead()) { $item['categories'][] = 'user/-/state/com.google/read'; @@ -527,69 +534,117 @@ function entriesToArray($entries) { } $tagNames = isset($entryIdsTagNames['e_' . $entry->id()]) ? $entryIdsTagNames['e_' . $entry->id()] : array(); foreach ($tagNames as $tagName) { - $item['categories'][] = 'user/-/label/' . $tagName; + $item['categories'][] = 'user/-/label/' . htmlspecialchars_decode($tagName, ENT_QUOTES); } $items[] = $item; } return $items; } -function streamContents($path, $include_target, $start_time, $count, $order, $exclude_target, $continuation) { -//http://code.google.com/p/pyrfeed/wiki/GoogleReaderAPI -//http://blog.martindoms.com/2009/10/16/using-the-google-reader-api-part-2/#feed - header('Content-Type: application/json; charset=UTF-8'); - - switch ($path) { - case 'reading-list': - $type = 'A'; - break; - case 'starred': - $type = 's'; - break; - case 'feed': - $type = 'f'; +function streamContentsFilters($type, $streamId, $filter_target, $exclude_target, $start_time, $stop_time) { + switch ($type) { + case 'f': //feed + if ($streamId != '' && !ctype_digit($streamId)) { + $feedDAO = FreshRSS_Factory::createFeedDao(); + $streamId = htmlspecialchars($streamId, ENT_COMPAT, 'UTF-8'); + $feed = $feedDAO->searchByUrl($streamId); + $streamId = $feed == null ? -1 : $feed->id(); + } break; - case 'label': + case 'c': //category or label $categoryDAO = FreshRSS_Factory::createCategoryDao(); - $cat = $categoryDAO->searchByName($include_target); + $streamId = htmlspecialchars($streamId, ENT_COMPAT, 'UTF-8'); + $cat = $categoryDAO->searchByName($streamId); if ($cat != null) { $type = 'c'; - $include_target = $cat->id(); + $streamId = $cat->id(); } else { $tagDAO = FreshRSS_Factory::createTagDao(); - $tag = $tagDAO->searchByName($include_target); + $tag = $tagDAO->searchByName($streamId); if ($tag != null) { $type = 't'; - $include_target = $tag->id(); + $streamId = $tag->id(); } else { $type = 'A'; - $include_target = -1; + $streamId = -1; } } break; + } + + switch ($filter_target) { + case 'user/-/state/com.google/read': + $state = FreshRSS_Entry::STATE_READ; + break; + case 'user/-/state/com.google/unread': + $state = FreshRSS_Entry::STATE_NOT_READ; + break; + case 'user/-/state/com.google/starred': + $state = FreshRSS_Entry::STATE_FAVORITE; + break; default: - $type = 'A'; + $state = FreshRSS_Entry::STATE_ALL; break; } switch ($exclude_target) { case 'user/-/state/com.google/read': - $state = FreshRSS_Entry::STATE_NOT_READ; + $state &= FreshRSS_Entry::STATE_NOT_READ; break; case 'user/-/state/com.google/unread': - $state = FreshRSS_Entry::STATE_READ; + $state &= FreshRSS_Entry::STATE_READ; + break; + case 'user/-/state/com.google/starred': + $state &= FreshRSS_Entry::STATE_NOT_FAVORITE; + break; + } + + $searches = new FreshRSS_BooleanSearch(''); + if ($start_time != '') { + $search = new FreshRSS_Search(''); + $search->setMinDate($start_time); + $searches->add($search); + } + if ($stop_time != '') { + $search = new FreshRSS_Search(''); + $search->setMaxDate($stop_time); + $searches->add($search); + } + + return array($type, $streamId, $state, $searches); +} + +function streamContents($path, $include_target, $start_time, $stop_time, $count, $order, $filter_target, $exclude_target, $continuation) { +//http://code.google.com/p/pyrfeed/wiki/GoogleReaderAPI +//http://blog.martindoms.com/2009/10/16/using-the-google-reader-api-part-2/#feed + header('Content-Type: application/json; charset=UTF-8'); + + switch ($path) { + case 'reading-list': + $type = 'A'; + break; + case 'starred': + $type = 's'; + break; + case 'feed': + $type = 'f'; + break; + case 'label': + $type = 'c'; break; default: - $state = FreshRSS_Entry::STATE_ALL; + $type = 'A'; break; } + list($type, $include_target, $state, $searches) = streamContentsFilters($type, $include_target, $filter_target, $exclude_target, $start_time, $stop_time); + if ($continuation != '') { $count++; //Shift by one element } $entryDAO = FreshRSS_Factory::createEntryDao(); - $entries = $entryDAO->listWhere($type, $include_target, $state, $order === 'o' ? 'ASC' : 'DESC', $count, $continuation, new FreshRSS_BooleanSearch(''), $start_time); + $entries = $entryDAO->listWhere($type, $include_target, $state, $order === 'o' ? 'ASC' : 'DESC', $count, $continuation, $searches); $items = entriesToArray($entries); @@ -614,7 +669,7 @@ function streamContents($path, $include_target, $start_time, $count, $order, $ex exit(); } -function streamContentsItemsIds($streamId, $start_time, $count, $order, $exclude_target, $continuation) { +function streamContentsItemsIds($streamId, $start_time, $stop_time, $count, $order, $filter_target, $exclude_target, $continuation) { //http://code.google.com/p/google-reader-api/wiki/ApiStreamItemsIds //http://code.google.com/p/pyrfeed/wiki/GoogleReaderAPI //http://blog.martindoms.com/2009/10/16/using-the-google-reader-api-part-2/#feed @@ -622,55 +677,32 @@ function streamContentsItemsIds($streamId, $start_time, $count, $order, $exclude $id = ''; if ($streamId === 'user/-/state/com.google/reading-list') { $type = 'A'; - } elseif ('user/-/state/com.google/starred') { + } elseif ($streamId === 'user/-/state/com.google/starred') { $type = 's'; } elseif (strpos($streamId, 'feed/') === 0) { $type = 'f'; - $id = basename($streamId); + $streamId = substr($streamId, 5); } elseif (strpos($streamId, 'user/-/label/') === 0) { $type = 'c'; - $c_name = substr($streamId, 13); - $categoryDAO = FreshRSS_Factory::createCategoryDao(); - $cat = $categoryDAO->searchByName($c_name); - if ($cat != null) { - $type = 'c'; - $id = $cat->id(); - } else { - $tagDAO = FreshRSS_Factory::createTagDao(); - $tag = $tagDAO->searchByName($c_name); - if ($tag != null) { - $type = 't'; - $id = $tag->id(); - } else { - $type = 'A'; - $id = -1; - } - } + $streamId = substr($streamId, 13); } - switch ($exclude_target) { - case 'user/-/state/com.google/read': - $state = FreshRSS_Entry::STATE_NOT_READ; - break; - default: - $state = FreshRSS_Entry::STATE_ALL; - break; - } + list($type, $id, $state, $searches) = streamContentsFilters($type, $streamId, $filter_target, $exclude_target, $start_time, $stop_time); if ($continuation != '') { $count++; //Shift by one element } $entryDAO = FreshRSS_Factory::createEntryDao(); - $ids = $entryDAO->listIdsWhere($type, $id, $state, $order === 'o' ? 'ASC' : 'DESC', $count, $continuation, new FreshRSS_BooleanSearch(''), $start_time); + $ids = $entryDAO->listIdsWhere($type, $id, $state, $order === 'o' ? 'ASC' : 'DESC', $count, $continuation, $searches); if ($continuation != '') { array_shift($ids); //Discard first element that was already sent in the previous response $count--; } - if (empty($ids)) { //For News+ bug https://github.com/noinnion/newsplus/issues/84#issuecomment-57834632 - $ids[] = 0; + if (empty($ids) && isset($_GET['client']) && $_GET['client'] === 'newsplus') { + $ids[] = 0; //For News+ bug https://github.com/noinnion/newsplus/issues/84#issuecomment-57834632 } $itemRefs = array(); foreach ($ids as $id) { @@ -697,7 +729,10 @@ function streamContentsItems($e_ids, $order) { header('Content-Type: application/json; charset=UTF-8'); foreach ($e_ids as $i => $e_id) { - $e_ids[$i] = hex2dec(basename($e_id)); //Strip prefix 'tag:google.com,2005:reader/item/' + if (strpos($e_id, '/') !== null) { + $e_id = hex2dec(basename($e_id)); //Strip prefix 'tag:google.com,2005:reader/item/' + } + $e_ids[$i] = $e_id; } $entryDAO = FreshRSS_Factory::createEntryDao(); @@ -717,7 +752,10 @@ function streamContentsItems($e_ids, $order) { function editTag($e_ids, $a, $r) { foreach ($e_ids as $i => $e_id) { - $e_ids[$i] = hex2dec(basename($e_id)); //Strip prefix 'tag:google.com,2005:reader/item/' + if (strpos($e_id, '/') !== null) { + $e_id = hex2dec(basename($e_id)); //Strip prefix 'tag:google.com,2005:reader/item/' + } + $e_ids[$i] = $e_id; } $entryDAO = FreshRSS_Factory::createEntryDao(); @@ -748,6 +786,7 @@ function editTag($e_ids, $a, $r) { } } if ($tagName != '') { + $tagName = htmlspecialchars($tagName, ENT_COMPAT, 'UTF-8'); $tag = $tagDAO->searchByName($tagName); if ($tag == null) { $tagDAO->addTag(array('name' => $tagName)); @@ -771,6 +810,7 @@ function editTag($e_ids, $a, $r) { default: if (strpos($r, 'user/-/label/') === 0) { $tagName = substr($r, 13); + $tagName = htmlspecialchars($tagName, ENT_COMPAT, 'UTF-8'); $tag = $tagDAO->searchByName($tagName); if ($tag != null) { foreach ($e_ids as $e_id) { @@ -788,7 +828,9 @@ function renameTag($s, $dest) { if ($s != '' && strpos($s, 'user/-/label/') === 0 && $dest != '' && strpos($dest, 'user/-/label/') === 0) { $s = substr($s, 13); + $s = htmlspecialchars($s, ENT_COMPAT, 'UTF-8'); $dest = substr($dest, 13); + $dest = htmlspecialchars($dest, ENT_COMPAT, 'UTF-8'); $categoryDAO = FreshRSS_Factory::createCategoryDao(); $cat = $categoryDAO->searchByName($s); @@ -810,6 +852,7 @@ function renameTag($s, $dest) { function disableTag($s) { if ($s != '' && strpos($s, 'user/-/label/') === 0) { $s = substr($s, 13); + $s = htmlspecialchars($s, ENT_COMPAT, 'UTF-8'); $categoryDAO = FreshRSS_Factory::createCategoryDao(); $cat = $categoryDAO->searchByName($s); if ($cat != null) { @@ -838,6 +881,7 @@ function markAllAsRead($streamId, $olderThanId) { $entryDAO->markReadFeed($f_id, $olderThanId); } elseif (strpos($streamId, 'user/-/label/') === 0) { $c_name = substr($streamId, 13); + $c_name = htmlspecialchars($c_name, ENT_COMPAT, 'UTF-8'); $categoryDAO = FreshRSS_Factory::createCategoryDao(); $cat = $categoryDAO->searchByName($c_name); if ($cat != null) { @@ -902,12 +946,14 @@ if (count($pathInfos) < 3) { * exclude items from a particular feed (obviously not useful in this * request, but xt appears in other listing requests). */ $exclude_target = isset($_GET['xt']) ? $_GET['xt'] : ''; + $filter_target = isset($_GET['it']) ? $_GET['it'] : ''; $count = isset($_GET['n']) ? intval($_GET['n']) : 20; //n=[integer] : The maximum number of results to return. $order = isset($_GET['r']) ? $_GET['r'] : 'd'; //r=[d|n|o] : Sort order of item results. d or n gives items in descending date order, o in ascending order. /* ot=[unix timestamp] : The time from which you want to retrieve * items. Only items that have been crawled by Google Reader after * this time will be returned. */ $start_time = isset($_GET['ot']) ? intval($_GET['ot']) : 0; + $stop_time = isset($_GET['nt']) ? intval($_GET['nt']) : 0; /* Continuation token. If a StreamContents response does not represent * all items in a timestamp range, it will have a continuation attribute. * The same request can be re-issued with the value of that attribute put @@ -920,23 +966,31 @@ if (count($pathInfos) < 3) { if (isset($pathInfos[7])) { if ($pathInfos[6] === 'feed') { $include_target = $pathInfos[7]; - StreamContents($pathInfos[6], $include_target, $start_time, $count, $order, $exclude_target, $continuation); + if ($include_target != '' && !ctype_digit($include_target)) { + $include_target = empty($_SERVER['REQUEST_URI']) ? '' : $_SERVER['REQUEST_URI']; + if (preg_match('#/reader/api/0/stream/contents/feed/([A-Za-z0-9\'!*()%$_.~+-]+)#', $include_target, $matches) && isset($matches[1])) { + $include_target = urldecode($matches[1]); + } else { + $include_target = ''; + } + } + streamContents($pathInfos[6], $include_target, $start_time, $stop_time, $count, $order, $filter_target, $exclude_target, $continuation); } elseif ($pathInfos[6] === 'user' && isset($pathInfos[8]) && isset($pathInfos[9])) { if ($pathInfos[8] === 'state') { if ($pathInfos[9] === 'com.google' && isset($pathInfos[10])) { if ($pathInfos[10] === 'reading-list' || $pathInfos[10] === 'starred') { $include_target = ''; - streamContents($pathInfos[10], $include_target, $start_time, $count, $order, $exclude_target, $continuation); + streamContents($pathInfos[10], $include_target, $start_time, $stop_time, $count, $order, $filter_target, $exclude_target, $continuation); } } } elseif ($pathInfos[8] === 'label') { $include_target = $pathInfos[9]; - streamContents($pathInfos[8], $include_target, $start_time, $count, $order, $exclude_target, $continuation); + streamContents($pathInfos[8], $include_target, $start_time, $stop_time, $count, $order, $filter_target, $exclude_target, $continuation); } } } else { //EasyRSS $include_target = ''; - streamContents('reading-list', $include_target, $start_time, $count, $order, $exclude_target, $continuation); + streamContents('reading-list', $include_target, $start_time, $stop_time, $count, $order, $filter_target, $exclude_target, $continuation); } } elseif ($pathInfos[5] === 'items') { if ($pathInfos[6] === 'ids' && isset($_GET['s'])) { @@ -944,7 +998,7 @@ if (count($pathInfos) < 3) { * be repeated to fetch the item IDs from multiple streams at once * (more efficient from a backend perspective than multiple requests). */ $streamId = $_GET['s']; - streamContentsItemsIds($streamId, $start_time, $count, $order, $exclude_target, $continuation); + streamContentsItemsIds($streamId, $start_time, $stop_time, $count, $order, $filter_target, $exclude_target, $continuation); } else if ($pathInfos[6] === 'contents' && isset($_POST['i'])) { //FeedMe $e_ids = multiplePosts('i'); //item IDs streamContentsItems($e_ids, $order); -- cgit v1.2.3 From 0fce9892ff2b03083706b4f78495539861db98aa Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Mon, 12 Nov 2018 09:03:20 +0100 Subject: API encoding tuning (#2120) Use only minimal XML->Unicode encoding for articles title. Follow-up of https://github.com/FreshRSS/FreshRSS/pull/2093 --- app/Models/Feed.php | 2 +- lib/lib_rss.php | 21 +++++++++++++-------- p/api/greader.php | 8 ++++---- 3 files changed, 18 insertions(+), 13 deletions(-) (limited to 'app') diff --git a/app/Models/Feed.php b/app/Models/Feed.php index a5ef33d6b..acf3bd981 100644 --- a/app/Models/Feed.php +++ b/app/Models/Feed.php @@ -424,7 +424,7 @@ class FreshRSS_Feed extends Minz_Model { $author_names = ''; if (is_array($authors)) { foreach ($authors as $author) { - $author_names .= escapeToUnicodeAlternative(strip_tags($author->name == '' ? $author->email : $author->name)) . '; '; + $author_names .= escapeToUnicodeAlternative(strip_tags($author->name == '' ? $author->email : $author->name), true) . '; '; } } $author_names = substr($author_names, 0, -2); diff --git a/lib/lib_rss.php b/lib/lib_rss.php index 52e4408d2..c445874c8 100644 --- a/lib/lib_rss.php +++ b/lib/lib_rss.php @@ -102,16 +102,21 @@ function safe_ascii($text) { return filter_var($text, FILTER_DEFAULT, FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH); } -function escapeToUnicodeAlternative($text) { +function escapeToUnicodeAlternative($text, $extended = true) { $text = htmlspecialchars_decode($text, ENT_QUOTES); + + //Problematic characters + $problem = array('&', '<', '>'); + //Use their fullwidth Unicode form instead: + $replace = array('&', '<', '>'); + // https://raw.githubusercontent.com/mihaip/google-reader-api/master/wiki/StreamId.wiki - return trim(str_replace( - //Problematic characters - array("'", '"', '^', '<', '>', '?', '&', '\\', '/', ',', ';'), - //Use their fullwidth Unicode form instead: - array("’", '"', '^', '<', '>', '?', '&', '\', '/', ',', ';'), - $text - )); + if ($extended) { + $problem += array("'", '"', '^', '?', '\\', '/', ',', ';'); + $replace += array("’", '"', '^', '?', '\', '/', ',', ';'); + } + + return trim(str_replace($problem, $replace, $text)); } /** diff --git a/p/api/greader.php b/p/api/greader.php index 7c5c54951..7cd312f2c 100644 --- a/p/api/greader.php +++ b/p/api/greader.php @@ -300,7 +300,7 @@ function subscriptionList() { foreach ($res as $line) { $subscriptions[] = array( 'id' => 'feed/' . $line['id'], - 'title' => escapeToUnicodeAlternative($line['name']), + 'title' => escapeToUnicodeAlternative($line['name'], true), 'categories' => array( array( 'id' => 'user/-/label/' . htmlspecialchars_decode($line['c_name'], ENT_QUOTES), @@ -506,7 +506,7 @@ function entriesToArray($entries) { 'crawlTimeMsec' => substr($entry->id(), 0, -3), 'timestampUsec' => '' . $entry->id(), //EasyRSS 'published' => $entry->date(true), - 'title' => escapeToUnicodeAlternative($entry->title()), + 'title' => escapeToUnicodeAlternative($entry->title(), false), 'summary' => array('content' => $entry->content()), 'alternate' => array( array('href' => htmlspecialchars_decode($entry->link(), ENT_QUOTES)), @@ -517,14 +517,14 @@ function entriesToArray($entries) { ), 'origin' => array( 'streamId' => 'feed/' . $f_id, - 'title' => escapeToUnicodeAlternative($f_name), //EasyRSS + 'title' => escapeToUnicodeAlternative($f_name, true), //EasyRSS //'htmlUrl' => $line['f_website'], ), ); $author = $entry->authors(true); $author = trim($author, '; '); if ($author != '') { - $item['author'] = escapeToUnicodeAlternative($author); + $item['author'] = escapeToUnicodeAlternative($author, false); } if ($entry->isRead()) { $item['categories'][] = 'user/-/state/com.google/read'; -- cgit v1.2.3 From adcbfc43b866ad2654be3f96e90c650cae224141 Mon Sep 17 00:00:00 2001 From: romibi Date: Tue, 13 Nov 2018 22:29:03 +0100 Subject: Improve long dropdown menu lists (#2108) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dropdown menus with lots of entries were a bit difficult to use … Set max height to 75% of Viewport-height and enabled scrolling --- app/views/helpers/index/normal/entry_bottom.phtml | 2 +- p/themes/base-theme/template.css | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) (limited to 'app') diff --git a/app/views/helpers/index/normal/entry_bottom.phtml b/app/views/helpers/index/normal/entry_bottom.phtml index 784a41e1f..1f35318e3 100644 --- a/app/views/helpers/index/normal/entry_bottom.phtml +++ b/app/views/helpers/index/normal/entry_bottom.phtml @@ -42,7 +42,7 @@ - + +
    + onlyFeedsWithError): ?> +
  • + +
  • + +
feed) ? ' class="active"' : ''; ?> -- cgit v1.2.3 From 2f70da06e6c19bef383e5011e6300eef7a8257cb Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Tue, 4 Dec 2018 21:26:10 +0100 Subject: Fix undefined conf (#2163) Small fix for https://github.com/FreshRSS/FreshRSS/pull/2137 --- app/Models/Auth.php | 1 + 1 file changed, 1 insertion(+) (limited to 'app') diff --git a/app/Models/Auth.php b/app/Models/Auth.php index d1e26b8e8..9c3e31952 100644 --- a/app/Models/Auth.php +++ b/app/Models/Auth.php @@ -233,6 +233,7 @@ class FreshRSS_FormAuth { $token_file = DATA_PATH . '/tokens/' . $token . '.txt'; $mtime = @filemtime($token_file); + $conf = Minz_Configuration::get('system'); $limits = $conf->limits; $cookie_duration = empty($limits['cookie_duration']) ? 2592000 : $limits['cookie_duration']; if ($mtime + $cookie_duration < time()) { -- cgit v1.2.3 From 2610db6260c8beeb9590cdc9f4f3aac1e9985510 Mon Sep 17 00:00:00 2001 From: Patrick Crandol Date: Tue, 11 Dec 2018 15:21:59 -0500 Subject: Remove rawurlencode from movim sharing (#2177) --- app/shares.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/shares.php b/app/shares.php index 55deed785..6d234b374 100644 --- a/app/shares.php +++ b/app/shares.php @@ -64,7 +64,7 @@ return array( ), 'movim' => array( 'url' => '~URL~/?share/~LINK~', - 'transform' => array('rawurlencode', 'urlencode'), + 'transform' => array('urlencode'), 'help' => 'https://github.com/edhelas/movim', 'form' => 'advanced', 'method' => 'GET', -- cgit v1.2.3 From a1071e7dd9b9efe3b1f61a3f6bb9f936ad6d7c6a Mon Sep 17 00:00:00 2001 From: Patrick Crandol Date: Tue, 11 Dec 2018 18:20:04 -0500 Subject: Add option to share to Pinboard. (#2178) * Prelim. Add Pinboard based on the url found in the "For iPad" section here: https://pinboard.in/howto/ * add i18n string for Pinboard * Add help to pinboard * add title to Pinboard Share Title seems to be an element, but isn't documented in the API. Perhaps it's an alternative to description? It probably won't hurt to include it. * Feedback from @samplereality * Minor Whitespace --- app/i18n/cz/gen.php | 1 + app/i18n/de/gen.php | 1 + app/i18n/en/gen.php | 1 + app/i18n/es/gen.php | 1 + app/i18n/fr/gen.php | 1 + app/i18n/he/gen.php | 1 + app/i18n/it/gen.php | 1 + app/i18n/kr/gen.php | 1 + app/i18n/nl/gen.php | 1 + app/i18n/oc/gen.php | 1 + app/i18n/pt-br/gen.php | 1 + app/i18n/ru/gen.php | 1 + app/i18n/tr/gen.php | 1 + app/i18n/zh-cn/gen.php | 1 + app/shares.php | 7 +++++++ 15 files changed, 21 insertions(+) (limited to 'app') diff --git a/app/i18n/cz/gen.php b/app/i18n/cz/gen.php index 27ff9414c..08fce0280 100644 --- a/app/i18n/cz/gen.php +++ b/app/i18n/cz/gen.php @@ -172,6 +172,7 @@ return array( 'linkedin' => 'LinkedIn', 'mastodon' => 'Mastodon', 'movim' => 'Movim', + 'pinboard' => 'Pinboard', 'pocket' => 'Pocket', 'print' => 'Tisk', 'shaarli' => 'Shaarli', diff --git a/app/i18n/de/gen.php b/app/i18n/de/gen.php index 59ce371d3..c02a55b2c 100644 --- a/app/i18n/de/gen.php +++ b/app/i18n/de/gen.php @@ -172,6 +172,7 @@ return array( 'linkedin' => 'LinkedIn', 'mastodon' => 'Mastodon', 'movim' => 'Movim', + 'pinboard' => 'Pinboard', 'pocket' => 'Pocket', 'print' => 'Drucken', 'shaarli' => 'Shaarli', diff --git a/app/i18n/en/gen.php b/app/i18n/en/gen.php index f0ce8ed02..32f5ee02e 100644 --- a/app/i18n/en/gen.php +++ b/app/i18n/en/gen.php @@ -172,6 +172,7 @@ return array( 'linkedin' => 'LinkedIn', 'mastodon' => 'Mastodon', 'movim' => 'Movim', + 'pinboard' => 'Pinboard', 'pocket' => 'Pocket', 'print' => 'Print', 'shaarli' => 'Shaarli', diff --git a/app/i18n/es/gen.php b/app/i18n/es/gen.php index 183e3f794..db36e5f5b 100755 --- a/app/i18n/es/gen.php +++ b/app/i18n/es/gen.php @@ -172,6 +172,7 @@ return array( 'linkedin' => 'LinkedIn', 'mastodon' => 'Mastodon', 'movim' => 'Movim', + 'pinboard' => 'Pinboard', 'pocket' => 'Pocket', 'print' => 'Print', 'shaarli' => 'Shaarli', diff --git a/app/i18n/fr/gen.php b/app/i18n/fr/gen.php index fa4dceadd..86d8461e6 100644 --- a/app/i18n/fr/gen.php +++ b/app/i18n/fr/gen.php @@ -172,6 +172,7 @@ return array( 'linkedin' => 'LinkedIn', 'mastodon' => 'Mastodon', 'movim' => 'Movim', + 'pinboard' => 'Pinboard', 'pocket' => 'Pocket', 'print' => 'Imprimer', 'shaarli' => 'Shaarli', diff --git a/app/i18n/he/gen.php b/app/i18n/he/gen.php index 9349cbce7..cf4a1fcda 100644 --- a/app/i18n/he/gen.php +++ b/app/i18n/he/gen.php @@ -172,6 +172,7 @@ return array( 'linkedin' => 'LinkedIn', 'mastodon' => 'Mastodon', 'movim' => 'Movim', + 'pinboard' => 'Pinboard', 'pocket' => 'Pocket', 'print' => 'הדפסה', 'shaarli' => 'Shaarli', diff --git a/app/i18n/it/gen.php b/app/i18n/it/gen.php index 3bf2f155f..9cc40ffe3 100644 --- a/app/i18n/it/gen.php +++ b/app/i18n/it/gen.php @@ -172,6 +172,7 @@ return array( 'linkedin' => 'LinkedIn', 'mastodon' => 'Mastodon', 'movim' => 'Movim', + 'pinboard' => 'Pinboard', 'pocket' => 'Pocket', 'print' => 'Stampa', 'shaarli' => 'Shaarli', diff --git a/app/i18n/kr/gen.php b/app/i18n/kr/gen.php index 744ec6fce..86a50e9c4 100644 --- a/app/i18n/kr/gen.php +++ b/app/i18n/kr/gen.php @@ -172,6 +172,7 @@ return array( 'linkedin' => 'LinkedIn', 'mastodon' => 'Mastodon', 'movim' => 'Movim', + 'pinboard' => 'Pinboard', 'pocket' => 'Pocket', 'print' => '인쇄', 'shaarli' => 'Shaarli', diff --git a/app/i18n/nl/gen.php b/app/i18n/nl/gen.php index 99b1cf9c2..bdf2e0abd 100644 --- a/app/i18n/nl/gen.php +++ b/app/i18n/nl/gen.php @@ -172,6 +172,7 @@ return array( 'linkedin' => 'LinkedIn', 'mastodon' => 'Mastodon', 'movim' => 'Movim', + 'pinboard' => 'Pinboard', 'pocket' => 'Pocket', 'print' => 'Print', 'shaarli' => 'Shaarli', diff --git a/app/i18n/oc/gen.php b/app/i18n/oc/gen.php index 246a74eb2..ffe10941d 100644 --- a/app/i18n/oc/gen.php +++ b/app/i18n/oc/gen.php @@ -172,6 +172,7 @@ return array( 'linkedin' => 'LinkedIn', 'mastodon' => 'Mastodon', 'movim' => 'Movim', + 'pinboard' => 'Pinboard', 'pocket' => 'Pocket', 'print' => 'Imprimir', 'shaarli' => 'Shaarli', diff --git a/app/i18n/pt-br/gen.php b/app/i18n/pt-br/gen.php index 8a0b26b3d..46ae53eb4 100644 --- a/app/i18n/pt-br/gen.php +++ b/app/i18n/pt-br/gen.php @@ -172,6 +172,7 @@ return array( 'linkedin' => 'LinkedIn', 'mastodon' => 'Mastodon', 'movim' => 'Movim', + 'pinboard' => 'Pinboard', 'pocket' => 'Pocket', 'print' => 'Imprimir', 'shaarli' => 'Shaarli', diff --git a/app/i18n/ru/gen.php b/app/i18n/ru/gen.php index f6b1f16ec..b55c6b667 100644 --- a/app/i18n/ru/gen.php +++ b/app/i18n/ru/gen.php @@ -172,6 +172,7 @@ return array( 'linkedin' => 'LinkedIn', 'mastodon' => 'Mastodon', 'movim' => 'Movim', + 'pinboard' => 'Pinboard', 'pocket' => 'Pocket', 'print' => 'Print', 'shaarli' => 'Shaarli', diff --git a/app/i18n/tr/gen.php b/app/i18n/tr/gen.php index 370660aba..a84c39f20 100644 --- a/app/i18n/tr/gen.php +++ b/app/i18n/tr/gen.php @@ -172,6 +172,7 @@ return array( 'linkedin' => 'LinkedIn', 'mastodon' => 'Mastodon', 'movim' => 'Movim', + 'pinboard' => 'Pinboard', 'pocket' => 'Pocket', 'print' => 'Print', 'shaarli' => 'Shaarli', diff --git a/app/i18n/zh-cn/gen.php b/app/i18n/zh-cn/gen.php index a0f74fdfa..1dcd95233 100644 --- a/app/i18n/zh-cn/gen.php +++ b/app/i18n/zh-cn/gen.php @@ -172,6 +172,7 @@ return array( 'linkedin' => 'LinkedIn', 'mastodon' => 'Mastodon', 'movim' => 'Movim', + 'pinboard' => 'Pinboard', 'pocket' => 'Pocket', 'print' => '打印', 'shaarli' => 'Shaarli', diff --git a/app/shares.php b/app/shares.php index 6d234b374..4f7fde3ed 100644 --- a/app/shares.php +++ b/app/shares.php @@ -137,4 +137,11 @@ return array( 'form' => 'simple', 'method' => 'GET', ), + 'pinboard' => array( + 'url' => 'https://pinboard.in/add?next=same&url=~LINK~&title=~TITLE~', + 'transform' => array('urlencode'), + 'help' => 'https://pinboard.in/api/', + 'form' => 'simple', + 'method' => 'GET', + ), ); -- cgit v1.2.3 From 478d3e6611e73974bd3b917c625bf0d099cbf123 Mon Sep 17 00:00:00 2001 From: Patrick Crandol Date: Wed, 12 Dec 2018 06:04:31 -0500 Subject: Decouple scrolling of feeds and articles (#2117) * Remove Sticky Kit Remove sticky-kit, as functionality is unsuitable for separate scrolling. * Remove sticky-kit load in main.js Further removing the sticky-kit kruft * Finish removal of references to Sticky-kit * CSS Changes to template for Independent Scrolling * Addition of JS and supporting CSS and html * More CSS fixes to establish expected behavior Should be able to scroll navbar without it being in sticky mode now. * Fix typo in main.js sortcut.js -> shortcut.js * Fix unexpected tree scrolling behavior * Change name of generic JS function * Improve sticky-aside * CSS changes in themes to accommodate independent Scroll In themes where .aside's width is not the standard 300px, .tree's width must be specified to be equal to .aside * Remove Sticky-Kit from README files * Updates to Sticky-Aside * Update Template to fix screwup * Make Recalculating height actually work * Let sidebar fill height if nav buttons aren't visible * Make accommodating for nav buttons actually work * update Swage theme for Independent Scroll feature * Integrate sticky_aside into main.js * Add Simple Scrollbar * Patch scrollbar color for themes with dark colored asides * Increase Visibility of scrollbar on Dark Themes * Improve async loading, events, and performance * CSS typo * Fix double scrollbar on mobile * Fix regression causing sticky to not be removed * No $ for non-jQuery variables * Fix strange condition + option for nice scrollbar * Initial attempt to use css sticky * Add stickyfill * make the correct element sticky * re-add incorrectly removed sidebar code * Continue fixing mistaken deletions * decrease frequency of recalc * use minified version of simple-scrollbar. * Load stickyfill instead of injecting * put recalc back where it belongs * re-remove script injector * remove padding bottom padding was causing the last item in the feed to be hidden under the nav buttons * Manual merge of css_scrollbar Add auto-detection of -webkit-scrollbar-thumb, otherwise fall back to simple-scrollbar.js * Fix Regression Sticky recalc is still needed when using css scrollbars * Replace method of closing dropdowns Changed from an overlay href to a javascript solution, for better compatibility * Remove Treepadding Treepadding was causing dropdown menus at the bottom of the tree to be obscured. * Undo unnecessary move of dropdown-target * Move Dropdown Handler to a sensibleish place * Fix light Scrollbar color not picking up on Firefox * Minor syntax * Minor Clarification of CSS/ patch BlueLagoon * Change logic for native WebKit scrollbar detection Fix https://github.com/FreshRSS/FreshRSS/pull/2117#issuecomment-444251419 Tested with Firefox 63, Firefox 65, Chrome 71, IE11, Edge 42 * Fixes for other views E.g. reader view * Cleaner way of generating hash for dropdowns * Make dropdown-toggle an actual toggle * Prepare for CSS Scrollbars Module Level 1 * Fix regression causing my labels dropdown not to appear * remove unneeded dropdown-close css rule * Re-apply some lost changes https://github.com/FreshRSS/FreshRSS/pull/2117/commits/3c509989e890b88852e52c67c1c5507d1e0bf28c * Add standard scrollbar compatibility E.g. Firefox 64+ * Make All dropdowns click-to-close * Remove BlueLagoon template * Try to fix Firefox https://github.com/FreshRSS/FreshRSS/pull/2117#discussion_r239539984 * Add CSS rules necessary for consistent function of scrollbar in Firefox * Use inheritance to determine width * Use overlay scrollbar where possible * Test Reduced Listener * Fix Firefox 62 And show subtle scrollbar even when not hovering * Add margin at bottom To allow opening the menus https://github.com/FreshRSS/FreshRSS/pull/2117#issuecomment-444571218 * Minor - tab correction * Spaces -> tabs * Remove unneeded inheritance * Fix indenting * Revert bad merge * Messy WIP to make dropdowns work * Style Cleanup * Break it down * Lets try a move-it move-it * Update p/scripts/main.js Co-Authored-By: pattems * Update p/themes/BlueLagoon/BlueLagoon.css Co-Authored-By: pattems * separate ALL THE THINGS * erroneous commas * and to or * removing double condition that doesn't do what I want * More breaking down * fix var * Fix variable name again * Remove magic number https://github.com/FreshRSS/FreshRSS/pull/2117#discussion_r240052598 * Suuuuper inelegant solution for dropdowns * lets try that again * Inelegant Fix For Dropdowns Now with 100% more working dropdowns * Make sidebar dropdowns work correctly? * Fix reversion * Make JS scrollbar always visible * Remove unneeded CSS Added early in this pull request, didn't get pulled out when it was obsolete * Fix CSS removal I missed * CSS comment update/consistency for dark themes * Clean Up Duplicate code * Make dropdowns properly hide Downside: Can no longer click on header/nav buttons to close * Strip unneeded if statement * jshint -W018 https://github.com/FreshRSS/FreshRSS/pull/2117#discussion_r240392851 * Half-reversion to old dropdown * make overlay href scale to sidebar width * remove init of nonexistent method * remove trailing tabs * move #close href where it belongs in index.phtml * Revert all changes to index.phtml * remove whitespace accidentally added in last commit * Move var's in init_column categories * Finish putting old style dropdowns back * Make CSS changes to use support statements * Cleanup Whitespace * re-add missing class * spaces -> tabs in main.js * tabs -> spaces css * Minor whitespace * Cleanup per @Alkarex * Second attempt to add bottom margin https://github.com/FreshRSS/FreshRSS/pull/2117#discussion_r240820901 * Fix error in IE11 * Simple-scrollbar color match for dark themes --- README.fr.md | 4 +- README.md | 6 +- app/layout/aside_feed.phtml | 3 +- p/scripts/jquery.sticky-kit.min.js | 9 --- p/scripts/main.js | 116 +++++++++++++++++++++++------------ p/scripts/simple-scrollbar.min.js | 2 + p/themes/BlueLagoon/BlueLagoon.css | 26 ++++++++ p/themes/Dark/dark.css | 25 ++++++++ p/themes/Flat/flat.css | 25 ++++++++ p/themes/Screwdriver/screwdriver.css | 25 ++++++++ p/themes/Swage/swage.css | 26 ++++++++ p/themes/Swage/swage.scss | 38 ++++++++++-- p/themes/base-theme/template.css | 99 +++++++++++++++++++++++++++--- 13 files changed, 338 insertions(+), 66 deletions(-) delete mode 100644 p/scripts/jquery.sticky-kit.min.js create mode 100644 p/scripts/simple-scrollbar.min.js (limited to 'app') diff --git a/README.fr.md b/README.fr.md index 312fbac45..a8a8f87ff 100644 --- a/README.fr.md +++ b/README.fr.md @@ -192,13 +192,13 @@ Tout client supportant une API de type Fever ; Sélection : * [php-http-304](https://alexandre.alapetite.fr/doc-alex/php-http-304/) * [jQuery](https://jquery.com/) * [lib_opml](https://github.com/marienfressinaud/lib_opml) -* [jQuery Plugin Sticky-Kit](https://leafo.net/sticky-kit/) * [keyboard_shortcuts](http://www.openjs.com/scripts/events/keyboard_shortcuts/) * [flotr2](http://www.humblesoftware.com/flotr2) -## Uniquement pour certaines options +## Uniquement pour certaines options ou configurations * [bcrypt.js](https://github.com/dcodeIO/bcrypt.js) * [phpQuery](https://github.com/phpquery/phpquery) +* [simple-scrollbar](https://github.com/buzinas/simple-scrollbar) ## Si les fonctions natives ne sont pas disponibles * [Services_JSON](https://pear.php.net/pepr/pepr-proposal-show.php?id=198) diff --git a/README.md b/README.md index 1e86d9f57..d6e13851e 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ FreshRSS comes with absolutely no warranty. More detailed information about installation and server configuration can be found in [our documentation](https://freshrss.github.io/FreshRSS/en/admins/02_Installation.html). -## Automated install +## Automated install * [![Docker](https://www.docker.com/sites/default/files/horizontal.png)](./Docker/) * [![YunoHost](https://install-app.yunohost.org/install-with-yunohost.png)](https://install-app.yunohost.org/?app=freshrss) * [![Cloudron](https://cloudron.io/img/button.svg)](https://cloudron.io/button.html?app=org.freshrss.cloudronapp) @@ -199,13 +199,13 @@ Supported clients are: * [php-http-304](https://alexandre.alapetite.fr/doc-alex/php-http-304/) * [jQuery](https://jquery.com/) * [lib_opml](https://github.com/marienfressinaud/lib_opml) -* [jQuery Plugin Sticky-Kit](https://leafo.net/sticky-kit/) * [keyboard_shortcuts](http://www.openjs.com/scripts/events/keyboard_shortcuts/) * [flotr2](http://www.humblesoftware.com/flotr2) -## Only for some options +## Only for some options or configurations * [bcrypt.js](https://github.com/dcodeIO/bcrypt.js) * [phpQuery](https://github.com/phpquery/phpquery) +* [simple-scrollbar](https://github.com/buzinas/simple-scrollbar) ## If native functions are not available * [Services_JSON](https://pear.php.net/pepr/pepr-proposal-show.php?id=198) diff --git a/app/layout/aside_feed.phtml b/app/layout/aside_feed.phtml index 7b1645ebd..73b200686 100644 --- a/app/layout/aside_feed.phtml +++ b/app/layout/aside_feed.phtml @@ -21,7 +21,7 @@
-
    + diff --git a/p/scripts/jquery.sticky-kit.min.js b/p/scripts/jquery.sticky-kit.min.js deleted file mode 100644 index e2a3c6de9..000000000 --- a/p/scripts/jquery.sticky-kit.min.js +++ /dev/null @@ -1,9 +0,0 @@ -/* - Sticky-kit v1.1.2 | WTFPL | Leaf Corcoran 2015 | http://leafo.net -*/ -(function(){var b,f;b=this.jQuery||window.jQuery;f=b(window);b.fn.stick_in_parent=function(d){var A,w,J,n,B,K,p,q,k,E,t;null==d&&(d={});t=d.sticky_class;B=d.inner_scrolling;E=d.recalc_every;k=d.parent;q=d.offset_top;p=d.spacer;w=d.bottoming;null==q&&(q=0);null==k&&(k=void 0);null==B&&(B=!0);null==t&&(t="is_stuck");A=b(document);null==w&&(w=!0);J=function(a,d,n,C,F,u,r,G){var v,H,m,D,I,c,g,x,y,z,h,l;if(!a.data("sticky_kit")){a.data("sticky_kit",!0);I=A.height();g=a.parent();null!=k&&(g=g.closest(k)); -if(!g.length)throw"failed to find stick parent";v=m=!1;(h=null!=p?p&&a.closest(p):b("
    "))&&h.css("position",a.css("position"));x=function(){var c,f,e;if(!G&&(I=A.height(),c=parseInt(g.css("border-top-width"),10),f=parseInt(g.css("padding-top"),10),d=parseInt(g.css("padding-bottom"),10),n=g.offset().top+c+f,C=g.height(),m&&(v=m=!1,null==p&&(a.insertAfter(h),h.detach()),a.css({position:"",top:"",width:"",bottom:""}).removeClass(t),e=!0),F=a.offset().top-(parseInt(a.css("margin-top"),10)||0)-q, -u=a.outerHeight(!0),r=a.css("float"),h&&h.css({width:a.outerWidth(!0),height:u,display:a.css("display"),"vertical-align":a.css("vertical-align"),"float":r}),e))return l()};x();if(u!==C)return D=void 0,c=q,z=E,l=function(){var b,l,e,k;if(!G&&(e=!1,null!=z&&(--z,0>=z&&(z=E,x(),e=!0)),e||A.height()===I||x(),e=f.scrollTop(),null!=D&&(l=e-D),D=e,m?(w&&(k=e+u+c>C+n,v&&!k&&(v=!1,a.css({position:"fixed",bottom:"",top:c}).trigger("sticky_kit:unbottom"))),eb&&!v&&(c-=l,c=Math.max(b-u,c),c=Math.min(q,c),m&&a.css({top:c+"px"})))):e>F&&(m=!0,b={position:"fixed",top:c},b.width="border-box"===a.css("box-sizing")?a.outerWidth()+"px":a.width()+"px",a.css(b).addClass(t),null==p&&(a.after(h),"left"!==r&&"right"!==r||h.append(a)),a.trigger("sticky_kit:stick")),m&&w&&(null==k&&(k=e+u+c>C+n),!v&&k)))return v=!0,"static"===g.css("position")&&g.css({position:"relative"}), -a.css({position:"absolute",bottom:d,top:"auto"}).trigger("sticky_kit:bottom")},y=function(){x();return l()},H=function(){G=!0;f.off("touchmove",l);f.off("scroll",l);f.off("resize",y);b(document.body).off("sticky_kit:recalc",y);a.off("sticky_kit:detach",H);a.removeData("sticky_kit");a.css({position:"",bottom:"",top:"",width:""});g.position("position","");if(m)return null==p&&("left"!==r&&"right"!==r||a.insertAfter(h),h.remove()),a.removeClass(t)},f.on("touchmove",l),f.on("scroll",l),f.on("resize", -y),b(document.body).on("sticky_kit:recalc",y),a.on("sticky_kit:detach",H),setTimeout(l,0)}};n=0;for(K=this.length;n +var sidebar = document.getElementById('sidebar'); +var useJsScrollbar = true; +try { + /*jshint -W018 */ + useJsScrollbar = sidebar && !CSS.supports('scrollbar-color: auto') && + !(parseInt(getComputedStyle(sidebar, '::-webkit-scrollbar').width) < sidebar.offsetWidth); + /*jshint +W018 */ +} catch (ex) { +} +if (useJsScrollbar) { + inject_script('simple-scrollbar.min.js'); +} + +function sticky_recalc() { + var h = 0; + if ($nav_entries && $nav_entries.length > 0) { + h = $(window).height() - sidebar.getBoundingClientRect().top - $nav_entries.height(); + } else { + h = $(window).height() - sidebar.getBoundingClientRect().top; + } + if (h > 0) { + $(sidebar).height(h); + } +} + +function init_simple_scrollbar() { + if (!window.SimpleScrollbar) { + if (window.console) { + console.log('FreshRSS waiting for simple-scrollbar…'); + } + window.setTimeout(init_simple_scrollbar, 100); + } else { + SimpleScrollbar.initEl(sidebar); + } +} +var scrollTimeout; +function init_sticky_sidebar(){ + if (!sidebar) { + return; + } + if (useJsScrollbar) { + init_simple_scrollbar(); + } + $(window).scroll(function () { + if (scrollTimeout) { + clearTimeout(scrollTimeout); + scrollTimeout = null; + } + scrollTimeout = setTimeout(sticky_recalc, 200); + }); + window.onresize = sticky_recalc; +} function init_confirm_action() { $('body').on('click', '.confirm', function () { @@ -1536,7 +1576,6 @@ function init_beforeDOM() { return; } if (['normal', 'reader', 'global'].indexOf(context.current_view) >= 0) { - inject_script('jquery.sticky-kit.min.js'); init_normal(); } } @@ -1554,6 +1593,7 @@ function init_afterDOM() { $stream = $('#stream'); if ($stream.length > 0) { init_load_more($stream); + init_sticky_sidebar(); init_posts(); init_nav_entries(); init_dynamic_tags(); diff --git a/p/scripts/simple-scrollbar.min.js b/p/scripts/simple-scrollbar.min.js new file mode 100644 index 000000000..36b7df0b8 --- /dev/null +++ b/p/scripts/simple-scrollbar.min.js @@ -0,0 +1,2 @@ +// https://github.com/buzinas/simple-scrollbar +!function(t,e){"object"==typeof exports?module.exports=e(window,document):t.SimpleScrollbar=e(window,document)}(this,function(t,e){function s(t){Object.prototype.hasOwnProperty.call(t,"data-simple-scrollbar")||Object.defineProperty(t,"data-simple-scrollbar",{value:new o(t)})}function i(t,s){function i(t){var e=t.pageY-a;a=t.pageY,n(function(){s.el.scrollTop+=e/s.scrollRatio})}function r(){t.classList.remove("ss-grabbed"),e.body.classList.remove("ss-grabbed"),e.removeEventListener("mousemove",i),e.removeEventListener("mouseup",r)}var a;t.addEventListener("mousedown",function(s){return a=s.pageY,t.classList.add("ss-grabbed"),e.body.classList.add("ss-grabbed"),e.addEventListener("mousemove",i),e.addEventListener("mouseup",r),!1})}function r(t){for(this.target=t,this.direction=window.getComputedStyle(this.target).direction,this.bar='
    ',this.wrapper=e.createElement("div"),this.wrapper.setAttribute("class","ss-wrapper"),this.el=e.createElement("div"),this.el.setAttribute("class","ss-content"),"rtl"===this.direction&&this.el.classList.add("rtl"),this.wrapper.appendChild(this.el);this.target.firstChild;)this.el.appendChild(this.target.firstChild);this.target.appendChild(this.wrapper),this.target.insertAdjacentHTML("beforeend",this.bar),this.bar=this.target.lastChild,i(this.bar,this),this.moveBar(),this.el.addEventListener("scroll",this.moveBar.bind(this)),this.el.addEventListener("mouseenter",this.moveBar.bind(this)),this.target.classList.add("ss-container");var s=window.getComputedStyle(t);"0px"===s.height&&"0px"!==s["max-height"]&&(t.style.height=s["max-height"])}function a(){for(var t=e.querySelectorAll("*[ss-container]"),i=0;i=1?i.bar.classList.add("ss-hidden"):(i.bar.classList.remove("ss-hidden"),i.bar.style.cssText="height:"+Math.max(100*i.scrollRatio,10)+"%; top:"+i.el.scrollTop/e*100+"%;right:"+a+"px;")})}},e.addEventListener("DOMContentLoaded",a),r.initEl=s,r.initAll=a;var o=r;return o}); \ No newline at end of file diff --git a/p/themes/BlueLagoon/BlueLagoon.css b/p/themes/BlueLagoon/BlueLagoon.css index f167f2051..263764a9c 100644 --- a/p/themes/BlueLagoon/BlueLagoon.css +++ b/p/themes/BlueLagoon/BlueLagoon.css @@ -564,6 +564,32 @@ a.btn { color: #0090FF } +/*=== Scrollbar */ + +@supports (scrollbar-width: thin) { + #sidebar { + scrollbar-color: rgba(255, 255, 255, 0.05) rgba(0, 0, 0, 0.0); + } + #sidebar:hover { + scrollbar-color: rgba(255, 255, 255, 0.3) rgba(0, 0, 0, 0.0); + } +} +.ss-scroll { + background: rgba(255, 255, 255, 0.1); +} +.ss-container:hover .ss-scroll, +.ss-container:active .ss-scroll { + background: rgba(255, 255, 255, 0.3); +} +@supports not (scrollbar-width: thin) { + #sidebar::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.1); + } + #sidebar:hover::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.3); + } +} + /*=== STRUCTURE */ /*===============*/ /*=== Header */ diff --git a/p/themes/Dark/dark.css b/p/themes/Dark/dark.css index 31ff514a2..b8366caeb 100644 --- a/p/themes/Dark/dark.css +++ b/p/themes/Dark/dark.css @@ -502,6 +502,31 @@ a.btn { color: #888; } +/*=== Scrollbar */ +@supports (scrollbar-width: thin) { + #sidebar { + scrollbar-color: rgba(255, 255, 255, 0.05) rgba(0, 0, 0, 0.0); + } + #sidebar:hover { + scrollbar-color: rgba(255, 255, 255, 0.3) rgba(0, 0, 0, 0.0); + } +} +.ss-scroll { + background: rgba(255, 255, 255, 0.1); +} +.ss-container:hover .ss-scroll, +.ss-container:active .ss-scroll { + background: rgba(255, 255, 255, 0.3); +} +@supports not (scrollbar-width: thin) { + #sidebar::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.1); + } + #sidebar:hover::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.3); + } +} + /*=== STRUCTURE */ /*===============*/ /*=== Header */ diff --git a/p/themes/Flat/flat.css b/p/themes/Flat/flat.css index be047a394..176a541f7 100644 --- a/p/themes/Flat/flat.css +++ b/p/themes/Flat/flat.css @@ -505,6 +505,31 @@ a.btn { color: #fff; } +/*=== Scrollbar */ +@supports (scrollbar-width: thin) { + #sidebar { + scrollbar-color: rgba(255, 255, 255, 0.05) rgba(0, 0, 0, 0.0); + } + #sidebar:hover { + scrollbar-color: rgba(255, 255, 255, 0.3) rgba(0, 0, 0, 0.0); + } +} +.ss-scroll { + background: rgba(255, 255, 255, 0.1); +} +.ss-container:hover .ss-scroll, +.ss-container:active .ss-scroll { + background: rgba(255, 255, 255, 0.3); +} +@supports not (scrollbar-width: thin) { + #sidebar::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.1); + } + #sidebar:hover::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.3); + } +} + /*=== STRUCTURE */ /*===============*/ /*=== Header */ diff --git a/p/themes/Screwdriver/screwdriver.css b/p/themes/Screwdriver/screwdriver.css index 1bc49c2db..b92dbac3e 100644 --- a/p/themes/Screwdriver/screwdriver.css +++ b/p/themes/Screwdriver/screwdriver.css @@ -559,6 +559,31 @@ a.btn { .tree-folder-items > .item.active > a { } +/*=== Scrollbar */ +@supports (scrollbar-width: thin) { + #sidebar { + scrollbar-color: rgba(255, 255, 255, 0.05) rgba(0, 0, 0, 0.0); + } + #sidebar:hover { + scrollbar-color: rgba(255, 255, 255, 0.3) rgba(0, 0, 0, 0.0); + } +} +.ss-scroll { + background: rgba(255, 255, 255, 0.1); +} +.ss-container:hover .ss-scroll, +.ss-container:active .ss-scroll { + background: rgba(255, 255, 255, 0.3); +} +@supports not (scrollbar-width: thin) { + #sidebar::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.1); + } + #sidebar:hover::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.3); + } +} + /*=== STRUCTURE */ /*===============*/ /*=== Header */ diff --git a/p/themes/Swage/swage.css b/p/themes/Swage/swage.css index 8f262da8c..a8ff0bdd5 100644 --- a/p/themes/Swage/swage.css +++ b/p/themes/Swage/swage.css @@ -529,6 +529,32 @@ text-decoration: none; color: #FCFCFC; } +@supports (scrollbar-width: thin) { + #sidebar { +scrollbar-color: rgba(255, 255, 255, 0.05) rgba(0, 0, 0, 0.0); +} +#sidebar:hover { +scrollbar-color: rgba(255, 255, 255, 0.3) rgba(0, 0, 0, 0.0); +} +} + +.ss-scroll { +background: rgba(255, 255, 255, 0.1); +} +.ss-container:hover .ss-scroll, +.ss-container:active .ss-scroll { +background: rgba(255, 255, 255, 0.3); +} + +@supports not (scrollbar-width: thin) { +#sidebar::-webkit-scrollbar-thumb { +background: rgba(255, 255, 255, 0.1); +} +#sidebar:hover::-webkit-scrollbar-thumb { +background: rgba(255, 255, 255, 0.3); +} +} + .header > .item { vertical-align: middle; } diff --git a/p/themes/Swage/swage.scss b/p/themes/Swage/swage.scss index c0c76cf14..8ddac14db 100644 --- a/p/themes/Swage/swage.scss +++ b/p/themes/Swage/swage.scss @@ -351,7 +351,7 @@ form { > a { min-width: initial; white-space: nowrap; - } + } &:hover { background: $color_nav; color: $color_light; @@ -603,6 +603,32 @@ form { } } +@supports (scrollbar-width: thin) { + #sidebar { + scrollbar-color: rgba(255, 255, 255, 0.05) rgba(0, 0, 0, 0.0); + } + #sidebar:hover { + scrollbar-color: rgba(255, 255, 255, 0.3) rgba(0, 0, 0, 0.0); + } +} + +.ss-scroll { + background: rgba(255, 255, 255, 0.1); +} +.ss-container:hover .ss-scroll, +.ss-container:active .ss-scroll { + background: rgba(255, 255, 255, 0.3); +} + +@supports not (scrollbar-width: thin) { + #sidebar::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.1); + } + #sidebar:hover::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.3); + } +} + .header { > .item { vertical-align: middle; @@ -918,7 +944,7 @@ form { #global { height: 0; } - + .header { height: 55px; background: $color_aside; @@ -936,7 +962,7 @@ form { color: $color_light; padding-left: 5px; } - + input { border-left: 5px solid; border-right: 1px darken( $color_light, 10%); @@ -984,7 +1010,7 @@ form { .dropdown-header, .dropdown-menu > .item { padding: 12px; } - + #new-article { width: 100%; bottom: initial; @@ -1207,5 +1233,5 @@ button.as-link { background: inital; } } - -} \ No newline at end of file + +} diff --git a/p/themes/base-theme/template.css b/p/themes/base-theme/template.css index fdc2f884e..60c4822ef 100644 --- a/p/themes/base-theme/template.css +++ b/p/themes/base-theme/template.css @@ -109,7 +109,7 @@ input[type="checkbox"] { min-height: 15px !important; } .dropdown-menu label > input[type="text"] { - with: 150px; + width: 150px; width: calc(99% - 5em); } .dropdown-menu input[type="checkbox"] { @@ -168,6 +168,13 @@ td.numeric { display: block; } +@supports (position: sticky) { + #mark-read-aside { + position: sticky; + top: 0; + } +} + /*=== Buttons */ .stick { display: inline-block; @@ -273,6 +280,7 @@ a.btn { left: 0; right: 0; display: block; z-index: -10; + cursor: default; } .separator { display: block; @@ -373,16 +381,90 @@ a.btn { cursor: grab; } +/*=== Scrollbar */ +.ss-wrapper { + overflow: hidden; + width: 100%; + height: 100%; + position: relative; + z-index: 1; + float: left; +} + +.ss-content { + height: 100%; + width: calc(100% + 18px); + padding: 0 0 0 0; + position: relative; + overflow-y: scroll; + box-sizing: border-box; +} + +.ss-content.rtl { + width: calc(100% + 18px); + right: auto; +} + +.ss-scroll { + position: relative; + background: rgba(0, 0, 0, 0.1); + width: 9px; + border-radius: 4px; + top: 0; + z-index: 2; + cursor: pointer; + transition: opacity 0.25s linear; +} + +.ss-hidden { + display: none; +} + +.ss-container:hover .ss-scroll, +.ss-container:active .ss-scroll { + background: rgba(0, 0, 0, 0.3); +} + +.ss-grabbed { + -o-user-select: none; + -ms-user-select: none; + -moz-user-select: none; + -webkit-user-select: none; + user-select: none; +} + +@supports (scrollbar-width: thin) { + #sidebar { + overflow-y: scroll; + scrollbar-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.05); + scrollbar-width: thin; + } + #sidebar:hover { + scrollbar-color: rgba(0, 0, 0, 0.3) rgba(0, 0, 0, 0.05); + } +} + +@supports not (scrollbar-width: thin) { + #sidebar::-webkit-scrollbar { + background: rgba(0, 0, 0, 0.05); + width: 8px; + } + #sidebar::-webkit-scrollbar-thumb { + background: rgba(0, 0, 0, 0.1); + border-radius: 5px; + display: unset; + } + #sidebar:hover::-webkit-scrollbar-thumb { + background: rgba(0, 0, 0, 0.3); + } +} + /*=== Tree */ .tree { margin: 0; - padding: 0 0 2em 0; list-style: none; text-align: left; -} - -.treepadding { - padding: 0 0 15em 0; + overflow-x: hidden; } .tree-folder-items { @@ -412,6 +494,10 @@ a.btn { white-space: nowrap; text-overflow: ellipsis; } +.tree-bottom { + visibility: hidden; + margin-bottom: 15em; +} /*=== STRUCTURE */ /*===============*/ @@ -914,7 +1000,6 @@ pre.enclosure-description { } .aside:target { width: 90%; - overflow: auto; } .flux_header .item.website { -- cgit v1.2.3 From 983aa587ee71a83d21b17de29d2a26763b18292e Mon Sep 17 00:00:00 2001 From: Patrick Crandol Date: Sat, 15 Dec 2018 04:56:38 -0500 Subject: Add aside_feed to reader view (#2180) * Add Nav menu to reader mode At this point, it appears to be non-functional in reader mode. * Add aside_feed toggle to reader view * make init_column_categories work in reader view * Make sidebar links redirect to proper view * CSS to support toggleable sidebar in reader view * remove unneeded !important --- app/layout/aside_feed.phtml | 15 ++++++++------- app/layout/nav_menu.phtml | 2 +- app/views/index/reader.phtml | 1 + p/scripts/main.js | 2 +- p/themes/base-theme/template.css | 23 +++++++++++++++++++++++ 5 files changed, 34 insertions(+), 9 deletions(-) (limited to 'app') diff --git a/app/layout/aside_feed.phtml b/app/layout/aside_feed.phtml index 73b200686..5efaa54d1 100644 --- a/app/layout/aside_feed.phtml +++ b/app/layout/aside_feed.phtml @@ -1,4 +1,5 @@ hide_read_feeds && FreshRSS_Context::isStateEnabled(FreshRSS_Entry::STATE_NOT_READ) && @@ -24,13 +25,13 @@
    - name(); ?> + name(); ?>
@@ -69,7 +70,7 @@
  • @@ -111,7 +112,7 @@