diff --git a/webfeed@xynium.github.com/atom.js b/webfeed@xynium.github.com/atom.js index 2f0800f..2f300cc 100644 --- a/webfeed@xynium.github.com/atom.js +++ b/webfeed@xynium.github.com/atom.js @@ -4,7 +4,7 @@ */ -var AtomParser=class AtomParser { +export class AtomParser { constructor(xmlBase){ this.Items =[]; this.Title =''; diff --git a/webfeed@xynium.github.com/extension.js b/webfeed@xynium.github.com/extension.js index 03f111e..531c584 100644 --- a/webfeed@xynium.github.com/extension.js +++ b/webfeed@xynium.github.com/extension.js @@ -1,454 +1,481 @@ /* * WebFeed - * MK3 by Xynium + * MK4 by Xynium — GNOME 49 ES Modules * Soup3 - * */ -'use strict'; +// GI imports (ES modules for GNOME 45+) +import GLib from 'gi://GLib'; +import Gio from 'gi://Gio'; +import St from 'gi://St'; +import Clutter from 'gi://Clutter'; +import GObject from 'gi://GObject'; +import Soup from 'gi://Soup?version=3.0'; -const Main = imports.ui.main; -const Me = imports.misc.extensionUtils.getCurrentExtension(); -const PanelMenu = imports.ui.panelMenu; -const PopupMenu = imports.ui.popupMenu; -const Util = imports.misc.util; -const ExtensionUtils = imports.misc.extensionUtils; -const JsxmL = Me.imports.jsxml; -const Rss = Me.imports.rss; -const Atom = Me.imports.atom; -const {GLib, Gio,St,Clutter,GObject,Soup} = imports.gi; +// GNOME Shell UI imports +import * as Main from 'resource:///org/gnome/shell/ui/main.js'; +import * as MessageTray from 'resource:///org/gnome/shell/ui/messageTray.js'; +import * as PanelMenu from 'resource:///org/gnome/shell/ui/panelMenu.js'; +import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js'; -const Gettext = imports.gettext.domain('webfeed'); -const _ = Gettext.gettext; +// Extension base class and i18n +import {Extension, gettext as _} from 'resource:///org/gnome/shell/extensions/extension.js'; +// Local modules +import {REXML} from './jsxml.js'; +import {RssParser} from './rss.js'; +import {AtomParser} from './atom.js'; + +// GSettings keys const RSS_FEEDS_LIST_KEY = 'rss-feeds-list'; const UPDATE_INTERVAL_KEY = 'update-interval'; const ITEMS_VISIBLE_KEY = 'items-visible'; -const DELETE_AFTER = "delete-after" -const OKFORNOTIF ="okfornotif"; -const DURHOTISHOT = "durationhotitem"; -const DLYFORRX ="delayforreceive"; - -const _MS_PER_HOUR = 1000 * 60 * 60 ; - -let webfeedClass; -let settings; -let feedsArray; -let rxAsync; -let secu; - -const WebFeedClass = GObject.registerClass( -class WebFeedClass extends PanelMenu.Button { - - _init() { +const DELETE_AFTER = 'delete-after'; +const OKFORNOTIF = 'okfornotif'; +const DURHOTISHOT = 'durationhotitem'; +const DLYFORRX = 'delayforreceive'; + +const _MS_PER_HOUR = 1000 * 60 * 60; + +// Common HTML named entities for decoding feed titles +const _HTML_ENTITIES = { + '&': '&', '<': '<', '>': '>', + '"': '"', ''': "'", ' ': ' ', + '«': '«', '»': '»', + '–': '–', '—': '—', + '…': '…', '©': '©', +}; + +/** + * Decode HTML entities in feed text (named, decimal, hex) and strip tags. + * RSS/Atom feeds often contain encoded characters like ’ or & + */ +function _decodeHtml(str) { + if (!str) return str; + let result = str.replace(/&[a-zA-Z]+;/g, m => _HTML_ENTITIES[m] ?? m); + result = result.replace(/&#(\d+);/g, (_m, code) => String.fromCodePoint(Number(code))); + result = result.replace(/&#x([0-9a-fA-F]+);/g, (_m, hex) => String.fromCodePoint(parseInt(hex, 16))); + result = result.replace(/<[^>]+>/g, ''); + return result; +} + +const WebFeedIndicator = GObject.registerClass( +class WebFeedIndicator extends PanelMenu.Button { + + _init(ext) { super._init(0); + // Store extension reference for settings, path and openPreferences() + this._ext = ext; + this._settings = ext.getSettings(); + this._dir = ext.path; + this.httpSession = null; this._startIndex = 0; - this.hotIndex=0; - + this.hotIndex = 0; + this._feedsArray = []; + this._rxAsync = []; + this._secu = 0; + + // Persistent notification source — shows as "WebFeed" in GNOME notifications + this._notifSource = new MessageTray.Source({ + title: 'WebFeed', + icon: Gio.icon_new_for_string(this._dir + '/rss_red.png'), + }); + Main.messageTray.add(this._notifSource); + this.topBox = new St.BoxLayout(); - // top panel button this.icon = new St.Icon({ - gicon : Gio.icon_new_for_string( Me.dir.get_path()+ '/rss_green.png' ), - style_class: 'webfeed-icon-size' + gicon: Gio.icon_new_for_string(this._dir + '/rss_green.png'), + style_class: 'webfeed-icon-size', }); - this.topBox.add_child(this.icon) + this.topBox.add_child(this.icon); this.add_child(this.topBox); - - //Menu + + // Menu this.feedsSection = new PopupMenu.PopupMenuSection(); this.menu.addMenuItem(this.feedsSection); - - //lign time - this.TimeMenu = new PopupMenu.PopupBaseMenuItem({ - reactive: false - }); + + // Time line + this.TimeMenu = new PopupMenu.PopupBaseMenuItem({reactive: false}); let customTimeBox = new St.BoxLayout({ - style_class: 'webfeed-time-box ', + style_class: 'webfeed-time-box', vertical: false, clip_to_allocation: true, x_align: Clutter.ActorAlign.CENTER, y_align: Clutter.ActorAlign.CENTER, reactive: false, x_expand: true, - pack_start: false }); - - this.lastUpdateTime = new St.Button({label: _("Last update")+': --:--'}); - customTimeBox.add_actor(this.lastUpdateTime); - this.TimeMenu.add_actor(customTimeBox); + + this.lastUpdateTime = new St.Button({label: _('Last update') + ': --:--'}); + customTimeBox.add_child(this.lastUpdateTime); + this.TimeMenu.add_child(customTimeBox); this.menu.addMenuItem(this.TimeMenu); - + let separator = new PopupMenu.PopupSeparatorMenuItem(); this.menu.addMenuItem(separator); - // buttons in bottom menu bar - this._buttonMenu = new PopupMenu.PopupBaseMenuItem({ - reactive: false - }); + // Bottom button bar + this._buttonMenu = new PopupMenu.PopupBaseMenuItem({reactive: false}); let customButtonBox = new St.BoxLayout({ - style_class: 'webfeed-button-box ', + style_class: 'webfeed-button-box', vertical: false, clip_to_allocation: true, x_align: Clutter.ActorAlign.CENTER, y_align: Clutter.ActorAlign.CENTER, reactive: true, x_expand: true, - pack_start: false }); - - let prevBtn = this.createRoundButton('go-previous'); - prevBtn.connect('clicked', () => { - this.onPreviousBtnClicked(); - }); - customButtonBox.add_actor(prevBtn); - - let nextBtn = this.createRoundButton('go-next'); - nextBtn.connect('clicked', () => { - this.onNextBtnClicked(); - }); - customButtonBox.add_actor(nextBtn); - - let reloadBtn = this.createRoundButton('view-refresh'); - reloadBtn.connect('clicked', () => { - this.realoadRssFeeds(); - }); - customButtonBox.add_actor(reloadBtn); - - let settingsBtn = this.createRoundButton('emblem-system'); - settingsBtn.connect('clicked', () => { - ExtensionUtils.openPrefs(); - }); - customButtonBox.add_actor(settingsBtn); - - this._buttonMenu.add_actor(customButtonBox); + let prevBtn = this._createRoundButton('go-previous'); + prevBtn.connect('clicked', () => this._onPreviousBtnClicked()); + customButtonBox.add_child(prevBtn); + + let nextBtn = this._createRoundButton('go-next'); + nextBtn.connect('clicked', () => this._onNextBtnClicked()); + customButtonBox.add_child(nextBtn); + + let reloadBtn = this._createRoundButton('view-refresh'); + reloadBtn.connect('clicked', () => this._reloadRssFeeds()); + customButtonBox.add_child(reloadBtn); + + let settingsBtn = this._createRoundButton('emblem-system'); + settingsBtn.connect('clicked', () => this._ext.openPreferences()); + customButtonBox.add_child(settingsBtn); + + this._buttonMenu.add_child(customButtonBox); this.menu.addMenuItem(this._buttonMenu); - + try { - // try to get default browser - this._browser = Gio.app_info_get_default_for_uri_scheme("http").get_executable(); //get_commandline(); - log("Browser : " + this._browser); + this._browser = Gio.app_info_get_default_for_uri_scheme('http').get_executable(); + console.log('Browser: ' + this._browser); + } catch (err) { + console.log(err + ' (get default browser error)'); } - catch (err) { - log(err + ' (get default browser error)'); - } - this.lastUpdateTime.set_label(_("Last update")+': ' + new Date().toLocaleTimeString()); - this.realoadRssFeeds(); + + this.lastUpdateTime.set_label(_('Last update') + ': ' + new Date().toLocaleTimeString()); + this._reloadRssFeeds(); } - - createRoundButton(iconName) { + + _createRoundButton(iconName) { let button = new St.Button(); button.child = new St.Icon({ icon_name: iconName, - style_class: 'webfeed-button-action' + style_class: 'webfeed-button-action', }); return button; } - // previous button clicked callback - onPreviousBtnClicked(){ - this._startIndex -= settings.get_int(ITEMS_VISIBLE_KEY); + _onPreviousBtnClicked() { + this._startIndex -= this._settings.get_int(ITEMS_VISIBLE_KEY); if (this._startIndex < 0) - this._startIndex = 0 - this.refreshMenuLst(); + this._startIndex = 0; + this._refreshMenuLst(); } - - // On next button clicked callback - onNextBtnClicked (){ - if (this._startIndex + settings.get_int(ITEMS_VISIBLE_KEY) < settings.get_strv(RSS_FEEDS_LIST_KEY).length){ - this._startIndex += settings.get_int(ITEMS_VISIBLE_KEY); - this._refreshExtensionUI(); + + _onNextBtnClicked() { + if (this._startIndex + this._settings.get_int(ITEMS_VISIBLE_KEY) < this._settings.get_strv(RSS_FEEDS_LIST_KEY).length) { + this._startIndex += this._settings.get_int(ITEMS_VISIBLE_KEY); + this._refreshMenuLst(); } } - /* - * Returns JSON object that represents HTTP (GET method) parameters - * stored in URL - * url - HTTP request URL - */ - getParametersAsJson(url) { - if (url.indexOf('?') == -1) - return "{}"; + _getParametersAsJson(url) { + if (url.indexOf('?') === -1) + return '{}'; - let urlParams = url.substr(url.indexOf('?') + 1); + let urlParams = url.substring(url.indexOf('?') + 1); let params = urlParams.split('&'); - let jsonObj = "{"; - for (let i = 0; i < params.length; i++){ + let jsonObj = '{'; + for (let i = 0; i < params.length; i++) { let pair = params[i].split('='); - jsonObj += '"' + pair[0] + '":' + '"' + pair[1] + '"'; - if (i != params.length -1) + jsonObj += '"' + pair[0] + '":"' + pair[1] + '"'; + if (i !== params.length - 1) jsonObj += ','; } - jsonObj += "}"; - + jsonObj += '}'; return jsonObj; } - //reload of RSS feeds from sources set in settings - realoadRssFeeds() { - log("Reload all Feeds"); - if (this.timeout) - GLib.source_remove(this.timeout); - - if (settings.get_strv(RSS_FEEDS_LIST_KEY).length!=0) { - feedsArray=[]; - rxAsync=[]; - if (settings.get_strv(RSS_FEEDS_LIST_KEY)) { - for (let i = 0; i < settings.get_strv(RSS_FEEDS_LIST_KEY).length; i++){ - let url = settings.get_strv(RSS_FEEDS_LIST_KEY)[i]; - let jsonObj = this.getParametersAsJson(url); - - if (url.indexOf('?') != -1) - url = url.substr(0, url.indexOf('?')); - - this.httpGetRequestAsync(url, JSON.parse(jsonObj), i); - rxAsync[i]=1; + // Fetch all RSS/Atom feeds and schedule next update + _reloadRssFeeds() { + console.log('Reload all Feeds'); + if (this._timeout) { + GLib.source_remove(this._timeout); + this._timeout = null; + } + + if (this._settings.get_strv(RSS_FEEDS_LIST_KEY).length !== 0) { + this._feedsArray = []; + this._rxAsync = []; + let feeds = this._settings.get_strv(RSS_FEEDS_LIST_KEY); + if (feeds) { + for (let i = 0; i < feeds.length; i++) { + let url = feeds[i]; + let jsonObj = this._getParametersAsJson(url); + + if (url.indexOf('?') !== -1) + url = url.substring(0, url.indexOf('?')); + + this._httpGetRequestAsync(url, JSON.parse(jsonObj), i); + this._rxAsync[i] = 1; } - } - //timer attente response - this.wtforresptmr=GLib.timeout_add(GLib.PRIORITY_HIGH_IDLE,100, this.wtforresp.bind(this)); - secu=0; + } + this._wtforresptmr = GLib.timeout_add(GLib.PRIORITY_HIGH_IDLE, 100, this._wtforresp.bind(this)); + this._secu = 0; } - //timeout if enabled - if (settings.get_int(UPDATE_INTERVAL_KEY) > 0) { - //log("Next scheduled reload after " + settings.get_int(UPDATE_INTERVAL_KEY)*60 + " seconds"); - this.timeout = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT_IDLE,settings.get_int(UPDATE_INTERVAL_KEY)*60, this.realoadRssFeeds.bind(this)); + + if (this._settings.get_int(UPDATE_INTERVAL_KEY) > 0) { + this._timeout = GLib.timeout_add_seconds( + GLib.PRIORITY_DEFAULT_IDLE, + this._settings.get_int(UPDATE_INTERVAL_KEY) * 60, + this._reloadRssFeeds.bind(this) + ); } return GLib.SOURCE_REMOVE; } - - //wait for all response from http get - //timer call it every 100ms - // when all received kill timer - // if after secu request not good throw an error an continue - wtforresp(){ - let strNoResp=""; - if (this.wtforresptmr) - GLib.source_remove(this.wtforresptmr); - let allz=false; - try{ - if (secu++>settings.get_int(DLYFORRX)) throw('ERROR : Http problem :'); // has waited too long - allz = rxAsync.every((value, index, array) =>{ - return value==0 ; - }); - if (!allz) { - this.wtforresptmr=GLib.timeout_add(GLib.PRIORITY_HIGH_IDLE,100, this.wtforresp.bind(this)); - return GLib.SOURCE_REMOVE; - } + + // Poll timer: wait for all HTTP responses (100ms interval) + _wtforresp() { + let strNoResp = ''; + if (this._wtforresptmr) { + GLib.source_remove(this._wtforresptmr); + this._wtforresptmr = null; } - catch(error){ - log(error); - for (let i = 0; i < settings.get_strv(RSS_FEEDS_LIST_KEY).length; i++) { - if (rxAsync[i]==1){ - //log(settings.get_strv(RSS_FEEDS_LIST_KEY)[i]+" has not responded "); - strNoResp+="\n"+settings.get_strv(RSS_FEEDS_LIST_KEY)[i]+_(" has not responded "); - } - } + let allz = false; + try { + if (this._secu++ > this._settings.get_int(DLYFORRX)) + throw new Error('ERROR: Http problem'); + + allz = this._rxAsync.every(value => value === 0); + if (!allz) { + this._wtforresptmr = GLib.timeout_add(GLib.PRIORITY_HIGH_IDLE, 100, this._wtforresp.bind(this)); + return GLib.SOURCE_REMOVE; + } + } catch (error) { + console.log(error); + let feeds = this._settings.get_strv(RSS_FEEDS_LIST_KEY); + for (let i = 0; i < feeds.length; i++) { + if (this._rxAsync[i] === 1) + strNoResp += '\n' + feeds[i] + _(' has not responded '); + } } - log('all response in '+secu/10+' s'); - this.refreshMenuLst(); - this.lastUpdateTime.set_label(_("Last update")+': ' + new Date().toLocaleTimeString()+strNoResp); + console.log('all response in ' + this._secu / 10 + ' s'); + this._refreshMenuLst(); + this.lastUpdateTime.set_label(_('Last update') + ': ' + new Date().toLocaleTimeString() + strNoResp); return GLib.SOURCE_REMOVE; } - /* - * Creates asynchronous HTTP GET request - * url - HTTP request URL without parameters - * params - JSON object of HTTP GET request parameters - * position - Position in RSS sources list - */ - //from https://libsoup.org/libsoup-3.0/client-basic.html - httpGetRequestAsync(url, params, position) { - if (this.httpSession == null) this.httpSession = new Soup.Session(); - let message = Soup.Message.new_from_encoded_form( 'GET', url, Soup.form_encode_hash(params)); - this.httpSession.send_and_read_async( message, GLib.PRIORITY_DEFAULT, null, (session, result) => { + // Async HTTP GET via Soup3 + _httpGetRequestAsync(url, params, position) { + if (this.httpSession === null) + this.httpSession = new Soup.Session(); + + let message = Soup.Message.new_from_encoded_form('GET', url, Soup.form_encode_hash(params)); + this.httpSession.send_and_read_async(message, GLib.PRIORITY_DEFAULT, null, (session, result) => { if (message.get_status() === Soup.Status.OK) { let bytes = session.send_and_read_finish(result); - if (bytes){ + if (bytes) { let decoder = new TextDecoder('utf-8'); let response = decoder.decode(bytes.get_data()); - onDownload(response, position); - //log(`Response: ${response}`); + this._onDownload(response, position); + } else { + this._rxAsync[position] = 0; } - else rxAsync[position]=0; - }else rxAsync[position]=0; - }); + } else { + this._rxAsync[position] = 0; + } + }); + } + + // Parse downloaded XML as RSS or Atom, decode HTML entities in titles + _onDownload(responseData, position) { + let xmlDoc = new REXML(responseData); + let feedParser = null; + + if (xmlDoc.rootElement.name.toLowerCase().slice(0, 3) === 'rss') + feedParser = new RssParser(xmlDoc.rootElement); + + if (xmlDoc.rootElement.name.toLowerCase().slice(0, 4) === 'feed') + feedParser = new AtomParser(xmlDoc.rootElement); + + if (feedParser === null) { + console.log('Bad XML nor RSS nor ATOM'); + this._rxAsync[position] = 0; + return; + } + + if (feedParser.Items.length > 0) { + let feed = { + Title: _decodeHtml(feedParser.Title), + HttpLink: feedParser.HttpLink, + PublishDate: feedParser.PublishDate, + Items: [], + }; + + for (let i = 0; i < feedParser.Items.length; i++) { + feed.Items.push({ + Title: _decodeHtml(feedParser.Items[i].Title), + HttpLink: feedParser.Items[i].HttpLink, + PublishDate: feedParser.Items[i].PublishDate, + }); + } + this._feedsArray[position] = feed; + this._rxAsync[position] = 0; + } else { + console.log('Bad XML or no item in it'); + this._rxAsync[position] = 0; + } } - // Reloads feeds section - refreshMenuLst() { + // Rebuild popup menu with feed items, sorted by date + _refreshMenuLst() { let counter = 0; this.feedsSection.removeAll(); - this.EraseHotItem(); - - for (let i = this._startIndex; i < feedsArray.length; i++) { - if (feedsArray[i] && feedsArray[i].Items) { - let old=((new Date()- this.ISODateParser(feedsArray[i].PublishDate)) / _MS_PER_HOUR); - if (old>settings.get_int(DELETE_AFTER)) continue; - if (this.hotIndex<1) { - this.warmItem(); + this._eraseHotItem(); + + for (let i = this._startIndex; i < this._feedsArray.length; i++) { + if (this._feedsArray[i] && this._feedsArray[i].Items) { + let feedDate = this._isoDateParser(this._feedsArray[i].PublishDate); + // Fallback: if feed-level date is missing/invalid, use newest item date + if (isNaN(feedDate.getTime()) && this._feedsArray[i].Items.length > 0) { + for (let k = 0; k < this._feedsArray[i].Items.length; k++) { + const itemDate = this._isoDateParser(this._feedsArray[i].Items[k].PublishDate); + if (!isNaN(itemDate.getTime()) && itemDate > feedDate || isNaN(feedDate.getTime())) + feedDate = itemDate; + } } - /*if ((old<(2*settings.get_int(UPDATE_INTERVAL_KEY)/60))&& (this.hotIndex<2)) { ne tient pas compte des titre paragraphe - this.hotItem(feedsArray[i].Title); - }*/ - let nItems = feedsArray[i].Items.length; - let subMenu = new PopupMenu.PopupSubMenuMenuItem(_("( ")+old.toFixed(1)+_("H ago) ")+feedsArray[i].Title+ ' (' + nItems + ') :') ; //(Encoder.htmlDecode(title) + ' (' + nitems + ')'); + let old = isNaN(feedDate.getTime()) ? 0 : (new Date() - feedDate) / _MS_PER_HOUR; + if (old > this._settings.get_int(DELETE_AFTER)) + continue; + if (this.hotIndex < 1) + this._warmItem(); + + // Sort items by publish date, newest first + const sortedItems = this._feedsArray[i].Items.slice().sort((a, b) => { + const da = this._isoDateParser(a.PublishDate); + const db = this._isoDateParser(b.PublishDate); + return db - da; + }); + + let nItems = sortedItems.length; + let timeStr = isNaN(feedDate.getTime()) ? '' : _('( ') + old.toFixed(1) + _('H ago) '); + let subMenu = new PopupMenu.PopupSubMenuMenuItem( + timeStr + this._feedsArray[i].Title + ' (' + nItems + ') :' + ); + for (let j = 0; j < nItems; j++) { - old=((new Date()- this.ISODateParser(feedsArray[i].Items[j].PublishDate)) / _MS_PER_HOUR); - if (old>settings.get_int(DELETE_AFTER)) continue; - if ((old<(settings.get_int(DURHOTISHOT)/60))&& (this.hotIndex<2)) { - this.hotItem(feedsArray[i].Items[j].Title); - } - let menuItem = new PopupMenu.PopupMenuItem( _("( ")+old.toFixed(1)+_("H ago) ")+feedsArray[i].Items[j].Title); //(Encoder.htmlDecode(title) + ' (' + nitems + ')'); + const item = sortedItems[j]; + old = (new Date() - this._isoDateParser(item.PublishDate)) / _MS_PER_HOUR; + if (old > this._settings.get_int(DELETE_AFTER)) + continue; + if (old < this._settings.get_int(DURHOTISHOT) / 60 && this.hotIndex < 2) + this._hotItem(item.Title); + + let menuItem = new PopupMenu.PopupMenuItem( + _('( ') + old.toFixed(1) + _('H ago) ') + item.Title + ); subMenu.menu.addMenuItem(menuItem); - //subMenu.menu.addAction( ("("+old.toFixed(1)+"H ago) "+feedsArray[i].Items[j].Title), null, 'view-refresh-symbolic'); - menuItem.connect('activate', ()=>{ - log("Opening browser : "+this._browser+" with link : " + feedsArray[i].Items[j].HttpLink); - try{ - Util.trySpawnCommandLine(this._browser + ' ' + feedsArray[i].Items[j].HttpLink); - } - catch (err) { - log(err + ' (launch browser error or snap install )'); - } + + // Open link in default browser on click + menuItem.connect('activate', () => { + console.log('Opening browser: ' + this._browser + ' with link: ' + item.HttpLink); + try { + let ctx = global.create_app_launch_context(0, -1); + let appInfo = Gio.AppInfo.create_from_commandline( + this._browser + ' ' + item.HttpLink, + null, + Gio.AppInfoCreateFlags.NONE + ); + appInfo.launch([], ctx); + } catch (err) { + console.log(err + ' (launch browser error)'); + } }); } - this.feedsSection.addMenuItem(subMenu); - } - else { - let subMenu = new PopupMenu.PopupMenuItem(_("No data available")); + this.feedsSection.addMenuItem(subMenu); + } else { + let subMenu = new PopupMenu.PopupMenuItem(_('No data available')); this.feedsSection.addMenuItem(subMenu); } counter++; - if (counter == settings.get_int(ITEMS_VISIBLE_KEY)) + if (counter === this._settings.get_int(ITEMS_VISIBLE_KEY)) break; } } - ISODateParser (datestr) { + _isoDateParser(datestr) { return new Date(datestr); } - - //notif new item - hotItem(strItm){ - this.hotIndex=2; + + // Hot item: red icon + notification (item newer than durationhotitem threshold) + _hotItem(strItm) { + this.hotIndex = 2; this.topBox.remove_all_children(); - this.icon = new St.Icon({ - gicon : Gio.icon_new_for_string( Me.dir.get_path()+ '/rss_red.png' ), - style_class: 'webfeed-icon-size' + this.icon = new St.Icon({ + gicon: Gio.icon_new_for_string(this._dir + '/rss_red.png'), + style_class: 'webfeed-icon-size', }); - this.topBox.add_child(this.icon) - if (settings.get_boolean(OKFORNOTIF)) - Main.notify("webfeed NEWS : "+strItm); + this.topBox.add_child(this.icon); + if (this._settings.get_boolean(OKFORNOTIF)) { + const notification = new MessageTray.Notification({ + source: this._notifSource, + title: 'WebFeed', + body: strItm, + }); + this._notifSource.addNotification(notification); + } } - - //il y a des reponses - warmItem(){ - this.hotIndex=1; - this.topBox.remove_all_children(); - this.icon = new St.Icon({ - gicon : Gio.icon_new_for_string( Me.dir.get_path()+ '/rss_yelow.png' ), - style_class: 'webfeed-icon-size' + + // Warm state: yellow icon (feeds loaded, no hot items) + _warmItem() { + this.hotIndex = 1; + this.topBox.remove_all_children(); + this.icon = new St.Icon({ + gicon: Gio.icon_new_for_string(this._dir + '/rss_yelow.png'), + style_class: 'webfeed-icon-size', }); - this.topBox.add_child(this.icon) + this.topBox.add_child(this.icon); } - - EraseHotItem(){ - this.hotIndex=0; + + // Default state: green icon (reset before each menu rebuild) + _eraseHotItem() { + this.hotIndex = 0; this.topBox.remove_all_children(); this.icon = new St.Icon({ - gicon : Gio.icon_new_for_string( Me.dir.get_path()+ '/rss_green.png' ), - style_class: 'webfeed-icon-size' + gicon: Gio.icon_new_for_string(this._dir + '/rss_green.png'), + style_class: 'webfeed-icon-size', }); - this.topBox.add_child(this.icon) - } - - destroy(){ - if (this.wtforresptmr) - GLib.source_remove(this.wtforresptmr); - if (this.timeout) - GLib.source_remove(this.timeout); - this.timeout=null; - this.wtforresptmr =null; + this.topBox.add_child(this.icon); + } + + // Cleanup timers on destroy + destroy() { + if (this._wtforresptmr) { + GLib.source_remove(this._wtforresptmr); + this._wtforresptmr = null; + } + if (this._timeout) { + GLib.source_remove(this._timeout); + this._timeout = null; + } super.destroy(); } }); -/* - * On HTTP request response callback - * responseData - response data - * position - Position in feed sources list - */ -function onDownload(responseData, position) { - let xmlDoc = new JsxmL.REXML(responseData); - let feedParser; - - if (xmlDoc.rootElement.name.toLowerCase().slice(0, 3) == 'rss'){ // 3 est la length de rss - //log('RSS '); - feedParser= new Rss.RssParser(xmlDoc.rootElement); - } - - if (xmlDoc.rootElement.name.toLowerCase().slice(0, 4) == 'feed'){ - //log('ATOM'); - feedParser= new Atom.AtomParser(xmlDoc.rootElement); - } - - if (feedParser==null) { // entré ni rss ni atom - log('Bad XML nor RSS nor ATOM'); - return; +// Extension entry point (GNOME 45+ ES module pattern) +export default class WebFeedExtension extends Extension { + enable() { + this._indicator = new WebFeedIndicator(this); + Main.panel.addToStatusArea(this.uuid, this._indicator, 0, 'right'); } - if (feedParser.Items.length > 0) - { - let Feed = new class{ - constructor(){ - this.Title= feedParser.Title; - this.HttpLink= feedParser.HttpLink; - this.PublishDate=feedParser.PublishDate; - this.Items= []; - } }; - - for (let i = 0; i < feedParser.Items.length; i++) { - let item = new class{ - constructor(){ - this.Title= feedParser.Items[i].Title; - this.HttpLink= feedParser.Items[i].HttpLink; - this.PublishDate=feedParser.Items[i].PublishDate; - }}; - Feed.Items.push(item); - } - feedsArray[position] = Feed; - rxAsync[position]=0; - } - else{ - log('Bad XML or no item in it'); - rxAsync[position]=0; - return; + disable() { + this._indicator?.destroy(); + this._indicator = null; } } - - -function init() { - ExtensionUtils.initTranslations('webfeed'); -} - - -function enable() { - settings = ExtensionUtils.getSettings('org.gnome.shell.extensions.webfeed'); - webfeedClass = new WebFeedClass(); - Main.panel.addToStatusArea('WebFeed', webfeedClass, 0, 'right'); -} - - -function disable() { - webfeedClass.destroy(); - webfeedClass=null; - settings=null; - feedsArray=null; - rxAsync=null; - secu=null; -} diff --git a/webfeed@xynium.github.com/jsxml.js b/webfeed@xynium.github.com/jsxml.js index 334acb8..cb2e27e 100644 --- a/webfeed@xynium.github.com/jsxml.js +++ b/webfeed@xynium.github.com/jsxml.js @@ -35,7 +35,7 @@ /////////////////////////////////////////////////////////////// -function REXML(XML) { +export function REXML(XML) { this.XML = XML; this.rootElement = null; diff --git a/webfeed@xynium.github.com/locale/es.po b/webfeed@xynium.github.com/locale/es.po new file mode 100644 index 0000000..0abc7aa --- /dev/null +++ b/webfeed@xynium.github.com/locale/es.po @@ -0,0 +1,121 @@ +# Spanish translation for WebFeed GNOME Shell extension +# Copyright (C) 2024 WebFeed contributors +# This file is distributed under the same license as the WebFeed package. +# +msgid "" +msgstr "" +"Project-Id-Version: webfeed 9\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-01 00:00+0000\n" +"PO-Revision-Date: 2024-12-01 00:00+0000\n" +"Last-Translator: \n" +"Language-Team: Spanish\n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: extension.js +msgid "( " +msgstr "( hace " + +#: extension.js +msgid "H ago) " +msgstr "H) " + +#: extension.js +msgid "No data available" +msgstr "Sin datos disponibles" + +#: extension.js +msgid " has not responded " +msgstr " no ha respondido " + +#: extension.js +msgid "Last update" +msgstr "Última actualización" + +#: prefs.js +msgid "General" +msgstr "General" + +#: prefs.js +msgid "Timing" +msgstr "Temporización" + +#: prefs.js +msgid "Update interval (minutes)" +msgstr "Intervalo de actualización (minutos)" + +#: prefs.js +msgid "0 = manual only" +msgstr "0 = solo manual" + +#: prefs.js +msgid "Waiting delay for response (seconds)" +msgstr "Tiempo de espera de respuesta (segundos)" + +#: prefs.js +msgid "Display" +msgstr "Visualización" + +#: prefs.js +msgid "RSS sources per page" +msgstr "Fuentes RSS por página" + +#: prefs.js +msgid "Erase after (hours)" +msgstr "Eliminar después de (horas)" + +#: prefs.js +msgid "Notifications" +msgstr "Notificaciones" + +#: prefs.js +msgid "Duration of news (minutes)" +msgstr "Duración de noticias (minutos)" + +#: prefs.js +msgid "Notification on news" +msgstr "Notificar noticias" + +#: prefs.js +msgid "Feeds" +msgstr "Fuentes" + +#: prefs.js +msgid "Feed sources" +msgstr "Fuentes de feeds" + +#: prefs.js +msgid "Add Feed" +msgstr "Añadir fuente" + +#: prefs.js +msgid "New Feed source" +msgstr "Nueva fuente" + +#: prefs.js +msgid "Enter feed URL:" +msgstr "Introduzca la URL del feed:" + +#: prefs.js +msgid "Cancel" +msgstr "Cancelar" + +#: prefs.js +msgid "Add" +msgstr "Añadir" + +#: prefs.js +msgid "Edit Feed source" +msgstr "Editar fuente" + +#: prefs.js +msgid "Edit feed URL:" +msgstr "Modificar la URL del feed:" + +#: prefs.js +msgid "Save" +msgstr "Guardar" diff --git a/webfeed@xynium.github.com/locale/es/LC_MESSAGES/webfeed.mo b/webfeed@xynium.github.com/locale/es/LC_MESSAGES/webfeed.mo new file mode 100644 index 0000000..480e096 Binary files /dev/null and b/webfeed@xynium.github.com/locale/es/LC_MESSAGES/webfeed.mo differ diff --git a/webfeed@xynium.github.com/locale/fr.po b/webfeed@xynium.github.com/locale/fr.po index bc73709..3b0209f 100644 --- a/webfeed@xynium.github.com/locale/fr.po +++ b/webfeed@xynium.github.com/locale/fr.po @@ -1,43 +1,121 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR , YEAR. +# French translation for WebFeed GNOME Shell extension +# Copyright (C) 2022 WebFeed contributors +# This file is distributed under the same license as the WebFeed package. # msgid "" msgstr "" -"Project-Id-Version: \n" +"Project-Id-Version: webfeed 9\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-09-08 13:55+0200\n" -"PO-Revision-Date: 2022-09-22 15:30+0200\n" +"POT-Creation-Date: 2024-12-01 00:00+0000\n" +"PO-Revision-Date: 2024-12-01 00:00+0000\n" "Last-Translator: \n" -"Language-Team: \n" +"Language-Team: French\n" "Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -"X-Generator: Poedit 3.0.1\n" -#: extension.js:299 extension.js:306 +#: extension.js msgid "( " -msgstr "( il y à : " +msgstr "( il y a : " -#: extension.js:299 extension.js:306 +#: extension.js msgid "H ago) " msgstr "H) " -#: extension.js:317 +#: extension.js msgid "No data available" -msgstr "Pas de données disponnibles" +msgstr "Pas de données disponibles" -#: prefs.js:138 +#: extension.js +msgid " has not responded " +msgstr " n'a pas répondu " + +#: extension.js +msgid "Last update" +msgstr "Dernière mise à jour" + +#: prefs.js +msgid "General" +msgstr "Général" + +#: prefs.js +msgid "Timing" +msgstr "Minuterie" + +#: prefs.js +msgid "Update interval (minutes)" +msgstr "Intervalle de mise à jour (minutes)" + +#: prefs.js +msgid "0 = manual only" +msgstr "0 = manuel uniquement" + +#: prefs.js +msgid "Waiting delay for response (seconds)" +msgstr "Délai d'attente de réponse (secondes)" + +#: prefs.js +msgid "Display" +msgstr "Affichage" + +#: prefs.js +msgid "RSS sources per page" +msgstr "Sources RSS par page" + +#: prefs.js +msgid "Erase after (hours)" +msgstr "Supprimer après (heures)" + +#: prefs.js +msgid "Notifications" +msgstr "Notifications" + +#: prefs.js +msgid "Duration of news (minutes)" +msgstr "Durée des nouvelles (minutes)" + +#: prefs.js +msgid "Notification on news" +msgstr "Notification de nouvelles" + +#: prefs.js +msgid "Feeds" +msgstr "Flux" + +#: prefs.js +msgid "Feed sources" +msgstr "Sources de flux" + +#: prefs.js +msgid "Add Feed" +msgstr "Ajouter un flux" + +#: prefs.js msgid "New Feed source" msgstr "Nouvelle source" -#: prefs.js:162 +#: prefs.js +msgid "Enter feed URL:" +msgstr "Entrez l'URL du flux :" + +#: prefs.js +msgid "Cancel" +msgstr "Annuler" + +#: prefs.js +msgid "Add" +msgstr "Ajouter" + +#: prefs.js msgid "Edit Feed source" -msgstr "Modifie la source" +msgstr "Modifier la source" -#: prefs.js:252 -msgid " has not responded " -msgstr " n'a pas repondu " +#: prefs.js +msgid "Edit feed URL:" +msgstr "Modifier l'URL du flux :" + +#: prefs.js +msgid "Save" +msgstr "Enregistrer" diff --git a/webfeed@xynium.github.com/locale/fr/LC_MESSAGES/webfeed.mo b/webfeed@xynium.github.com/locale/fr/LC_MESSAGES/webfeed.mo index 6f1dcdf..254df1d 100644 Binary files a/webfeed@xynium.github.com/locale/fr/LC_MESSAGES/webfeed.mo and b/webfeed@xynium.github.com/locale/fr/LC_MESSAGES/webfeed.mo differ diff --git a/webfeed@xynium.github.com/locale/nl.po b/webfeed@xynium.github.com/locale/nl.po index 85d7b5e..23dc885 100644 --- a/webfeed@xynium.github.com/locale/nl.po +++ b/webfeed@xynium.github.com/locale/nl.po @@ -1,25 +1,14 @@ -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -#: atom.js:10 atom.js:11 atom.js:12 atom.js:13 atom.js:43 atom.js:44 atom.js:45 -#: atom.js:46 atom.js:47 atom.js:48 jsxml.js:44 jsxml.js:50 jsxml.js:51 -#: jsxml.js:52 jsxml.js:53 jsxml.js:54 jsxml.js:55 jsxml.js:68 jsxml.js:82 -#: jsxml.js:83 jsxml.js:91 jsxml.js:92 jsxml.js:96 jsxml.js:101 jsxml.js:106 -#: jsxml.js:108 jsxml.js:110 jsxml.js:112 jsxml.js:116 jsxml.js:121 -#: jsxml.js:123 jsxml.js:147 jsxml.js:152 jsxml.js:168 jsxml.js:169 -#: jsxml.js:176 jsxml.js:181 jsxml.js:227 jsxml.js:246 jsxml.js:291 -#: jsxml.js:303 jsxml.js:306 jsxml.js:310 jsxml.js:314 jsxml.js:339 -#: jsxml.js:343 jsxml.js:350 jsxml.js:372 jsxml.js:373 jsxml.js:384 -#: jsxml.js:490 prefs.js:138 prefs.js:139 prefs.js:163 rss.js:10 rss.js:11 -#: rss.js:12 rss.js:13 rss.js:40 rss.js:41 rss.js:42 rss.js:43 rss.js:44 -#: rss.js:45 +# Dutch translation for WebFeed GNOME Shell extension +# Copyright (C) 2022 WebFeed contributors +# This file is distributed under the same license as the WebFeed package. # Heimen Stoffels , 2022. +# msgid "" msgstr "" -"Project-Id-Version: \n" +"Project-Id-Version: webfeed 9\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-09-08 13:55+0200\n" -"PO-Revision-Date: 2022-11-04 18:01+0100\n" +"POT-Creation-Date: 2024-12-01 00:00+0000\n" +"PO-Revision-Date: 2024-12-01 00:00+0000\n" "Last-Translator: Heimen Stoffels \n" "Language-Team: Dutch\n" "Language: nl\n" @@ -27,28 +16,107 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Lokalize 22.08.2\n" -#: extension.js:299 extension.js:306 +#: extension.js msgid "( " msgstr "( " -#: extension.js:299 extension.js:306 +#: extension.js msgid "H ago) " msgstr "uur geleden) " -#: extension.js:317 +#: extension.js msgid "No data available" msgstr "Er zijn geen gegevens beschikbaar" -#: prefs.js:138 -msgid "New Feed source" +#: extension.js +msgid " has not responded " +msgstr " is niet beschikbaar " + +#: extension.js +msgid "Last update" +msgstr "Laatst bijgewerkt" + +#: prefs.js +msgid "General" +msgstr "Algemeen" + +#: prefs.js +msgid "Timing" +msgstr "Timing" + +#: prefs.js +msgid "Update interval (minutes)" +msgstr "Bijwerkinterval (minuten)" + +#: prefs.js +msgid "0 = manual only" +msgstr "0 = alleen handmatig" + +#: prefs.js +msgid "Waiting delay for response (seconds)" +msgstr "Wachttijd voor antwoord (seconden)" + +#: prefs.js +msgid "Display" +msgstr "Weergave" + +#: prefs.js +msgid "RSS sources per page" +msgstr "RSS-bronnen per pagina" + +#: prefs.js +msgid "Erase after (hours)" +msgstr "Verwijderen na (uren)" + +#: prefs.js +msgid "Notifications" +msgstr "Meldingen" + +#: prefs.js +msgid "Duration of news (minutes)" +msgstr "Duur van nieuws (minuten)" + +#: prefs.js +msgid "Notification on news" +msgstr "Melding bij nieuws" + +#: prefs.js +msgid "Feeds" +msgstr "Feeds" + +#: prefs.js +msgid "Feed sources" +msgstr "Feedbronnen" + +#: prefs.js +msgid "Add Feed" msgstr "Feed toevoegen" -#: prefs.js:162 +#: prefs.js +msgid "New Feed source" +msgstr "Nieuwe feedbron" + +#: prefs.js +msgid "Enter feed URL:" +msgstr "Voer de feed-URL in:" + +#: prefs.js +msgid "Cancel" +msgstr "Annuleren" + +#: prefs.js +msgid "Add" +msgstr "Toevoegen" + +#: prefs.js msgid "Edit Feed source" msgstr "Feed bewerken" -#: prefs.js:252 -msgid " has not responded " -msgstr " is niet beschikbaar " +#: prefs.js +msgid "Edit feed URL:" +msgstr "Feed-URL bewerken:" + +#: prefs.js +msgid "Save" +msgstr "Opslaan" diff --git a/webfeed@xynium.github.com/locale/nl/LC_MESSAGES/nl.mo b/webfeed@xynium.github.com/locale/nl/LC_MESSAGES/nl.mo deleted file mode 100644 index b8cd6d2..0000000 Binary files a/webfeed@xynium.github.com/locale/nl/LC_MESSAGES/nl.mo and /dev/null differ diff --git a/webfeed@xynium.github.com/locale/nl/LC_MESSAGES/webfeed.mo b/webfeed@xynium.github.com/locale/nl/LC_MESSAGES/webfeed.mo new file mode 100644 index 0000000..febf993 Binary files /dev/null and b/webfeed@xynium.github.com/locale/nl/LC_MESSAGES/webfeed.mo differ diff --git a/webfeed@xynium.github.com/locale/pl.po b/webfeed@xynium.github.com/locale/pl.po new file mode 100644 index 0000000..4e4fb1e --- /dev/null +++ b/webfeed@xynium.github.com/locale/pl.po @@ -0,0 +1,121 @@ +# Polish translation for WebFeed GNOME Shell extension +# Copyright (C) 2024 WebFeed contributors +# This file is distributed under the same license as the WebFeed package. +# +msgid "" +msgstr "" +"Project-Id-Version: webfeed 9\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-01 00:00+0000\n" +"PO-Revision-Date: 2024-12-01 00:00+0000\n" +"Last-Translator: \n" +"Language-Team: Polish\n" +"Language: pl\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n" + +#: extension.js +msgid "( " +msgstr "( " + +#: extension.js +msgid "H ago) " +msgstr "godz. temu) " + +#: extension.js +msgid "No data available" +msgstr "Brak danych" + +#: extension.js +msgid " has not responded " +msgstr " nie odpowiedział " + +#: extension.js +msgid "Last update" +msgstr "Ostatnia aktualizacja" + +#: prefs.js +msgid "General" +msgstr "Ogólne" + +#: prefs.js +msgid "Timing" +msgstr "Czas" + +#: prefs.js +msgid "Update interval (minutes)" +msgstr "Interwał aktualizacji (minuty)" + +#: prefs.js +msgid "0 = manual only" +msgstr "0 = tylko ręcznie" + +#: prefs.js +msgid "Waiting delay for response (seconds)" +msgstr "Czas oczekiwania na odpowiedź (sekundy)" + +#: prefs.js +msgid "Display" +msgstr "Wyświetlanie" + +#: prefs.js +msgid "RSS sources per page" +msgstr "Źródeł RSS na stronę" + +#: prefs.js +msgid "Erase after (hours)" +msgstr "Usuń po (godziny)" + +#: prefs.js +msgid "Notifications" +msgstr "Powiadomienia" + +#: prefs.js +msgid "Duration of news (minutes)" +msgstr "Czas trwania wiadomości (minuty)" + +#: prefs.js +msgid "Notification on news" +msgstr "Powiadomienie o wiadomościach" + +#: prefs.js +msgid "Feeds" +msgstr "Kanały" + +#: prefs.js +msgid "Feed sources" +msgstr "Źródła kanałów" + +#: prefs.js +msgid "Add Feed" +msgstr "Dodaj kanał" + +#: prefs.js +msgid "New Feed source" +msgstr "Nowe źródło" + +#: prefs.js +msgid "Enter feed URL:" +msgstr "Wprowadź URL kanału:" + +#: prefs.js +msgid "Cancel" +msgstr "Anuluj" + +#: prefs.js +msgid "Add" +msgstr "Dodaj" + +#: prefs.js +msgid "Edit Feed source" +msgstr "Edytuj źródło" + +#: prefs.js +msgid "Edit feed URL:" +msgstr "Zmień URL kanału:" + +#: prefs.js +msgid "Save" +msgstr "Zapisz" diff --git a/webfeed@xynium.github.com/locale/pl/LC_MESSAGES/webfeed.mo b/webfeed@xynium.github.com/locale/pl/LC_MESSAGES/webfeed.mo new file mode 100644 index 0000000..5cb13a4 Binary files /dev/null and b/webfeed@xynium.github.com/locale/pl/LC_MESSAGES/webfeed.mo differ diff --git a/webfeed@xynium.github.com/locale/pt_BR.po b/webfeed@xynium.github.com/locale/pt_BR.po new file mode 100644 index 0000000..5fcd141 --- /dev/null +++ b/webfeed@xynium.github.com/locale/pt_BR.po @@ -0,0 +1,121 @@ +# Brazilian Portuguese translation for WebFeed GNOME Shell extension +# Copyright (C) 2024 WebFeed contributors +# This file is distributed under the same license as the WebFeed package. +# +msgid "" +msgstr "" +"Project-Id-Version: webfeed 9\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-01 00:00+0000\n" +"PO-Revision-Date: 2024-12-01 00:00+0000\n" +"Last-Translator: \n" +"Language-Team: Brazilian Portuguese\n" +"Language: pt_BR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: extension.js +msgid "( " +msgstr "( " + +#: extension.js +msgid "H ago) " +msgstr "H atrás) " + +#: extension.js +msgid "No data available" +msgstr "Sem dados disponíveis" + +#: extension.js +msgid " has not responded " +msgstr " não respondeu " + +#: extension.js +msgid "Last update" +msgstr "Última atualização" + +#: prefs.js +msgid "General" +msgstr "Geral" + +#: prefs.js +msgid "Timing" +msgstr "Temporização" + +#: prefs.js +msgid "Update interval (minutes)" +msgstr "Intervalo de atualização (minutos)" + +#: prefs.js +msgid "0 = manual only" +msgstr "0 = apenas manual" + +#: prefs.js +msgid "Waiting delay for response (seconds)" +msgstr "Tempo de espera pela resposta (segundos)" + +#: prefs.js +msgid "Display" +msgstr "Exibição" + +#: prefs.js +msgid "RSS sources per page" +msgstr "Fontes RSS por página" + +#: prefs.js +msgid "Erase after (hours)" +msgstr "Apagar após (horas)" + +#: prefs.js +msgid "Notifications" +msgstr "Notificações" + +#: prefs.js +msgid "Duration of news (minutes)" +msgstr "Duração das notícias (minutos)" + +#: prefs.js +msgid "Notification on news" +msgstr "Notificar sobre notícias" + +#: prefs.js +msgid "Feeds" +msgstr "Feeds" + +#: prefs.js +msgid "Feed sources" +msgstr "Fontes de feeds" + +#: prefs.js +msgid "Add Feed" +msgstr "Adicionar feed" + +#: prefs.js +msgid "New Feed source" +msgstr "Nova fonte" + +#: prefs.js +msgid "Enter feed URL:" +msgstr "Insira a URL do feed:" + +#: prefs.js +msgid "Cancel" +msgstr "Cancelar" + +#: prefs.js +msgid "Add" +msgstr "Adicionar" + +#: prefs.js +msgid "Edit Feed source" +msgstr "Editar fonte" + +#: prefs.js +msgid "Edit feed URL:" +msgstr "Alterar a URL do feed:" + +#: prefs.js +msgid "Save" +msgstr "Salvar" diff --git a/webfeed@xynium.github.com/locale/pt_BR/LC_MESSAGES/webfeed.mo b/webfeed@xynium.github.com/locale/pt_BR/LC_MESSAGES/webfeed.mo new file mode 100644 index 0000000..56e4a2f Binary files /dev/null and b/webfeed@xynium.github.com/locale/pt_BR/LC_MESSAGES/webfeed.mo differ diff --git a/webfeed@xynium.github.com/locale/ru.po b/webfeed@xynium.github.com/locale/ru.po new file mode 100644 index 0000000..048468e --- /dev/null +++ b/webfeed@xynium.github.com/locale/ru.po @@ -0,0 +1,121 @@ +# Russian translation for WebFeed GNOME Shell extension +# Copyright (C) 2024 WebFeed contributors +# This file is distributed under the same license as the WebFeed package. +# +msgid "" +msgstr "" +"Project-Id-Version: webfeed 9\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-01 00:00+0000\n" +"PO-Revision-Date: 2024-12-01 00:00+0000\n" +"Last-Translator: \n" +"Language-Team: Russian\n" +"Language: ru\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n" + +#: extension.js +msgid "( " +msgstr "( " + +#: extension.js +msgid "H ago) " +msgstr "ч назад) " + +#: extension.js +msgid "No data available" +msgstr "Нет данных" + +#: extension.js +msgid " has not responded " +msgstr " не ответил " + +#: extension.js +msgid "Last update" +msgstr "Обновлено" + +#: prefs.js +msgid "General" +msgstr "Основные" + +#: prefs.js +msgid "Timing" +msgstr "Таймеры" + +#: prefs.js +msgid "Update interval (minutes)" +msgstr "Интервал обновления (минуты)" + +#: prefs.js +msgid "0 = manual only" +msgstr "0 = только вручную" + +#: prefs.js +msgid "Waiting delay for response (seconds)" +msgstr "Ожидание ответа (секунды)" + +#: prefs.js +msgid "Display" +msgstr "Отображение" + +#: prefs.js +msgid "RSS sources per page" +msgstr "Источников на странице" + +#: prefs.js +msgid "Erase after (hours)" +msgstr "Удалять через (часы)" + +#: prefs.js +msgid "Notifications" +msgstr "Уведомления" + +#: prefs.js +msgid "Duration of news (minutes)" +msgstr "Срок новости (минуты)" + +#: prefs.js +msgid "Notification on news" +msgstr "Уведомлять о новостях" + +#: prefs.js +msgid "Feeds" +msgstr "Ленты" + +#: prefs.js +msgid "Feed sources" +msgstr "Источники лент" + +#: prefs.js +msgid "Add Feed" +msgstr "Добавить ленту" + +#: prefs.js +msgid "New Feed source" +msgstr "Новый источник" + +#: prefs.js +msgid "Enter feed URL:" +msgstr "Введите URL ленты:" + +#: prefs.js +msgid "Cancel" +msgstr "Отмена" + +#: prefs.js +msgid "Add" +msgstr "Добавить" + +#: prefs.js +msgid "Edit Feed source" +msgstr "Редактировать источник" + +#: prefs.js +msgid "Edit feed URL:" +msgstr "Измените URL ленты:" + +#: prefs.js +msgid "Save" +msgstr "Сохранить" diff --git a/webfeed@xynium.github.com/locale/ru/LC_MESSAGES/webfeed.mo b/webfeed@xynium.github.com/locale/ru/LC_MESSAGES/webfeed.mo new file mode 100644 index 0000000..54ed583 Binary files /dev/null and b/webfeed@xynium.github.com/locale/ru/LC_MESSAGES/webfeed.mo differ diff --git a/webfeed@xynium.github.com/locale/uk.po b/webfeed@xynium.github.com/locale/uk.po new file mode 100644 index 0000000..95156c2 --- /dev/null +++ b/webfeed@xynium.github.com/locale/uk.po @@ -0,0 +1,121 @@ +# Ukrainian translation for WebFeed GNOME Shell extension +# Copyright (C) 2024 WebFeed contributors +# This file is distributed under the same license as the WebFeed package. +# +msgid "" +msgstr "" +"Project-Id-Version: webfeed 9\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-01 00:00+0000\n" +"PO-Revision-Date: 2024-12-01 00:00+0000\n" +"Last-Translator: \n" +"Language-Team: Ukrainian\n" +"Language: uk\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n" + +#: extension.js +msgid "( " +msgstr "( " + +#: extension.js +msgid "H ago) " +msgstr "год тому) " + +#: extension.js +msgid "No data available" +msgstr "Немає даних" + +#: extension.js +msgid " has not responded " +msgstr " не відповів " + +#: extension.js +msgid "Last update" +msgstr "Оновлено" + +#: prefs.js +msgid "General" +msgstr "Загальні" + +#: prefs.js +msgid "Timing" +msgstr "Таймери" + +#: prefs.js +msgid "Update interval (minutes)" +msgstr "Інтервал оновлення (хвилини)" + +#: prefs.js +msgid "0 = manual only" +msgstr "0 = лише вручну" + +#: prefs.js +msgid "Waiting delay for response (seconds)" +msgstr "Очікування відповіді (секунди)" + +#: prefs.js +msgid "Display" +msgstr "Відображення" + +#: prefs.js +msgid "RSS sources per page" +msgstr "Джерел на сторінці" + +#: prefs.js +msgid "Erase after (hours)" +msgstr "Видаляти через (години)" + +#: prefs.js +msgid "Notifications" +msgstr "Сповіщення" + +#: prefs.js +msgid "Duration of news (minutes)" +msgstr "Термін новини (хвилини)" + +#: prefs.js +msgid "Notification on news" +msgstr "Сповіщати про новини" + +#: prefs.js +msgid "Feeds" +msgstr "Стрічки" + +#: prefs.js +msgid "Feed sources" +msgstr "Джерела стрічок" + +#: prefs.js +msgid "Add Feed" +msgstr "Додати стрічку" + +#: prefs.js +msgid "New Feed source" +msgstr "Нове джерело" + +#: prefs.js +msgid "Enter feed URL:" +msgstr "Введіть URL стрічки:" + +#: prefs.js +msgid "Cancel" +msgstr "Скасувати" + +#: prefs.js +msgid "Add" +msgstr "Додати" + +#: prefs.js +msgid "Edit Feed source" +msgstr "Редагувати джерело" + +#: prefs.js +msgid "Edit feed URL:" +msgstr "Змініть URL стрічки:" + +#: prefs.js +msgid "Save" +msgstr "Зберегти" diff --git a/webfeed@xynium.github.com/locale/uk/LC_MESSAGES/webfeed.mo b/webfeed@xynium.github.com/locale/uk/LC_MESSAGES/webfeed.mo new file mode 100644 index 0000000..c1a7b91 Binary files /dev/null and b/webfeed@xynium.github.com/locale/uk/LC_MESSAGES/webfeed.mo differ diff --git a/webfeed@xynium.github.com/locale/webfeed.pot b/webfeed@xynium.github.com/locale/webfeed.pot index f634c1b..091fa53 100644 --- a/webfeed@xynium.github.com/locale/webfeed.pot +++ b/webfeed@xynium.github.com/locale/webfeed.pot @@ -1,54 +1,120 @@ -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. +# WebFeed GNOME Shell extension translation template +# Copyright (C) 2024 WebFeed contributors +# This file is distributed under the same license as the WebFeed package. # -#: atom.js:10 atom.js:11 atom.js:12 atom.js:13 atom.js:43 atom.js:44 atom.js:45 -#: atom.js:46 atom.js:47 atom.js:48 jsxml.js:44 jsxml.js:50 jsxml.js:51 -#: jsxml.js:52 jsxml.js:53 jsxml.js:54 jsxml.js:55 jsxml.js:68 jsxml.js:82 -#: jsxml.js:83 jsxml.js:91 jsxml.js:92 jsxml.js:96 jsxml.js:101 jsxml.js:106 -#: jsxml.js:108 jsxml.js:110 jsxml.js:112 jsxml.js:116 jsxml.js:121 -#: jsxml.js:123 jsxml.js:147 jsxml.js:152 jsxml.js:168 jsxml.js:169 -#: jsxml.js:176 jsxml.js:181 jsxml.js:227 jsxml.js:246 jsxml.js:291 -#: jsxml.js:303 jsxml.js:306 jsxml.js:310 jsxml.js:314 jsxml.js:339 -#: jsxml.js:343 jsxml.js:350 jsxml.js:372 jsxml.js:373 jsxml.js:384 -#: jsxml.js:490 prefs.js:138 prefs.js:139 prefs.js:163 rss.js:10 rss.js:11 -#: rss.js:12 rss.js:13 rss.js:40 rss.js:41 rss.js:42 rss.js:43 rss.js:44 -#: rss.js:45 -# Heimen Stoffels , 2022. msgid "" msgstr "" -"Project-Id-Version: \n" +"Project-Id-Version: webfeed 9\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-09-08 13:55+0200\n" -"PO-Revision-Date: 2022-11-04 18:00+0100\n" -"Last-Translator: Heimen Stoffels \n" -"Language-Team: Dutch\n" -"Language: nl\n" +"POT-Creation-Date: 2024-12-01 00:00+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Lokalize 22.08.2\n" -#: extension.js:299 extension.js:306 +#: extension.js msgid "( " -msgstr "( " +msgstr "" -#: extension.js:299 extension.js:306 +#: extension.js msgid "H ago) " -msgstr "uur geleden) " +msgstr "" -#: extension.js:317 +#: extension.js msgid "No data available" -msgstr "Er zijn geen gegevens beschikbaar" +msgstr "" + +#: extension.js +msgid " has not responded " +msgstr "" + +#: extension.js +msgid "Last update" +msgstr "" + +#: prefs.js +msgid "General" +msgstr "" + +#: prefs.js +msgid "Timing" +msgstr "" + +#: prefs.js +msgid "Update interval (minutes)" +msgstr "" + +#: prefs.js +msgid "0 = manual only" +msgstr "" + +#: prefs.js +msgid "Waiting delay for response (seconds)" +msgstr "" + +#: prefs.js +msgid "Display" +msgstr "" + +#: prefs.js +msgid "RSS sources per page" +msgstr "" + +#: prefs.js +msgid "Erase after (hours)" +msgstr "" -#: prefs.js:138 +#: prefs.js +msgid "Notifications" +msgstr "" + +#: prefs.js +msgid "Duration of news (minutes)" +msgstr "" + +#: prefs.js +msgid "Notification on news" +msgstr "" + +#: prefs.js +msgid "Feeds" +msgstr "" + +#: prefs.js +msgid "Feed sources" +msgstr "" + +#: prefs.js +msgid "Add Feed" +msgstr "" + +#: prefs.js msgid "New Feed source" -msgstr "Feed toevoegen" +msgstr "" + +#: prefs.js +msgid "Enter feed URL:" +msgstr "" + +#: prefs.js +msgid "Cancel" +msgstr "" + +#: prefs.js +msgid "Add" +msgstr "" -#: prefs.js:162 +#: prefs.js msgid "Edit Feed source" -msgstr "Feed bewerken" +msgstr "" -#: prefs.js:252 -msgid " has not responded " -msgstr " is niet beschikbaar " +#: prefs.js +msgid "Edit feed URL:" +msgstr "" + +#: prefs.js +msgid "Save" +msgstr "" diff --git a/webfeed@xynium.github.com/locale/zh_CN.po b/webfeed@xynium.github.com/locale/zh_CN.po new file mode 100644 index 0000000..adcfdd2 --- /dev/null +++ b/webfeed@xynium.github.com/locale/zh_CN.po @@ -0,0 +1,121 @@ +# Simplified Chinese translation for WebFeed GNOME Shell extension +# Copyright (C) 2024 WebFeed contributors +# This file is distributed under the same license as the WebFeed package. +# +msgid "" +msgstr "" +"Project-Id-Version: webfeed 9\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-01 00:00+0000\n" +"PO-Revision-Date: 2024-12-01 00:00+0000\n" +"Last-Translator: \n" +"Language-Team: Chinese (Simplified)\n" +"Language: zh_CN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: extension.js +msgid "( " +msgstr "(" + +#: extension.js +msgid "H ago) " +msgstr "小时前)" + +#: extension.js +msgid "No data available" +msgstr "暂无数据" + +#: extension.js +msgid " has not responded " +msgstr " 未响应" + +#: extension.js +msgid "Last update" +msgstr "上次更新" + +#: prefs.js +msgid "General" +msgstr "常规" + +#: prefs.js +msgid "Timing" +msgstr "定时" + +#: prefs.js +msgid "Update interval (minutes)" +msgstr "更新间隔(分钟)" + +#: prefs.js +msgid "0 = manual only" +msgstr "0 = 仅手动" + +#: prefs.js +msgid "Waiting delay for response (seconds)" +msgstr "等待响应时间(秒)" + +#: prefs.js +msgid "Display" +msgstr "显示" + +#: prefs.js +msgid "RSS sources per page" +msgstr "每页RSS源数" + +#: prefs.js +msgid "Erase after (hours)" +msgstr "清除时间(小时)" + +#: prefs.js +msgid "Notifications" +msgstr "通知" + +#: prefs.js +msgid "Duration of news (minutes)" +msgstr "新闻时效(分钟)" + +#: prefs.js +msgid "Notification on news" +msgstr "新闻通知" + +#: prefs.js +msgid "Feeds" +msgstr "订阅源" + +#: prefs.js +msgid "Feed sources" +msgstr "订阅源列表" + +#: prefs.js +msgid "Add Feed" +msgstr "添加订阅" + +#: prefs.js +msgid "New Feed source" +msgstr "新订阅源" + +#: prefs.js +msgid "Enter feed URL:" +msgstr "输入订阅URL:" + +#: prefs.js +msgid "Cancel" +msgstr "取消" + +#: prefs.js +msgid "Add" +msgstr "添加" + +#: prefs.js +msgid "Edit Feed source" +msgstr "编辑订阅源" + +#: prefs.js +msgid "Edit feed URL:" +msgstr "修改订阅URL:" + +#: prefs.js +msgid "Save" +msgstr "保存" diff --git a/webfeed@xynium.github.com/locale/zh_CN/LC_MESSAGES/webfeed.mo b/webfeed@xynium.github.com/locale/zh_CN/LC_MESSAGES/webfeed.mo new file mode 100644 index 0000000..c540a69 Binary files /dev/null and b/webfeed@xynium.github.com/locale/zh_CN/LC_MESSAGES/webfeed.mo differ diff --git a/webfeed@xynium.github.com/metadata.json b/webfeed@xynium.github.com/metadata.json index 7670360..73ce66f 100644 --- a/webfeed@xynium.github.com/metadata.json +++ b/webfeed@xynium.github.com/metadata.json @@ -1,8 +1,9 @@ { "description": "RSS and Atom Feed reader for GNOME Shell", "name": "WebFeed", - "shell-version": [ "43", "44" ], + "shell-version": ["45", "46", "47", "48", "49", "50"], "url": "https://github.com/xynium/webfeed", + "settings-schema": "org.gnome.shell.extensions.webfeed", "uuid": "webfeed@xynium.github.com", - "version": 8 + "version": 9 } diff --git a/webfeed@xynium.github.com/prefs.js b/webfeed@xynium.github.com/prefs.js index c95cd1c..03c4f97 100644 --- a/webfeed@xynium.github.com/prefs.js +++ b/webfeed@xynium.github.com/prefs.js @@ -1,210 +1,277 @@ /* * WebFeed extension for GNOME Shell - * Xynium September 2022 + * Xynium — GNOME 49 ES Modules / Adw */ -'use strict'; -const { Gio, Gtk ,GObject} = imports.gi; -const ExtensionUtils = imports.misc.extensionUtils; -const Me = ExtensionUtils.getCurrentExtension(); -const Gettext = imports.gettext.domain('WebFeed'); -const _ = Gettext.gettext; -const COLUMN_ID = 0; -const MAX_UPDATE_INTERVAL = 1440; +// GI imports (GTK4 + libadwaita for preferences UI) +import Gio from 'gi://Gio'; +import Gtk from 'gi://Gtk'; +import Adw from 'gi://Adw'; +// Preferences base class and i18n (GNOME 45+) +import {ExtensionPreferences, gettext as _} from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js'; + +// GSettings keys const RSS_FEEDS_LIST_KEY = 'rss-feeds-list'; const UPDATE_INTERVAL_KEY = 'update-interval'; const ITEMS_VISIBLE_KEY = 'items-visible'; -const DELETE_AFTER = "delete-after" -const OKFORNOTIF ="okfornotif"; -const DURHOTISHOT = "durationhotitem"; -const DLYFORRX ="delayforreceive"; +const DELETE_AFTER = 'delete-after'; +const OKFORNOTIF = 'okfornotif'; +const DURHOTISHOT = 'durationhotitem'; +const DLYFORRX = 'delayforreceive'; +// Preferences window using Adw.PreferencesWindow (GNOME 45+ pattern) +export default class WebFeedPreferences extends ExtensionPreferences { + fillPreferencesWindow(window) { + const settings = this.getSettings(); -function init() { - ExtensionUtils.initTranslations('WebFeed'); -} + // --- Page: General --- + const page = new Adw.PreferencesPage({ + title: _('General'), + icon_name: 'preferences-system-symbolic', + }); + window.add(page); + // --- Group: Timing --- + const timingGroup = new Adw.PreferencesGroup({ + title: _('Timing'), + }); + page.add(timingGroup); -function buildPrefsWidget () { - return new PrefsWebFeed(); -} - - - - -const PrefsWebFeed = GObject.registerClass( - class PrefsWebFeed extends Gtk.Box { - - _init(params = {}) { - super._init(params); - - this.settings = ExtensionUtils.getSettings('org.gnome.shell.extensions.webfeed'); - let builder = new Gtk.Builder(); - builder.set_translation_domain('WebFeed'); - builder.add_from_file(Me.path + '/prefs.ui'); - - // update interval - let widjet0= builder.get_object("spbt1"); - widjet0.set_range(0, MAX_UPDATE_INTERVAL); - this.settings.bind(UPDATE_INTERVAL_KEY, widjet0, 'value', Gio.SettingsBindFlags.DEFAULT); - - // items visible per page - let widjet1 = builder.get_object('spbt2'); - this.settings.bind(ITEMS_VISIBLE_KEY, widjet1, 'value', Gio.SettingsBindFlags.DEFAULT); - - // delete after - let widjet3 = builder.get_object('spbtn3'); - this.settings.bind(DELETE_AFTER, widjet3, 'value', Gio.SettingsBindFlags.DEFAULT); - - // delais rx - let widjet4 = builder.get_object('spbtnRxDly'); - this.settings.bind(DLYFORRX, widjet4, 'value', Gio.SettingsBindFlags.DEFAULT); - - // delete news - let widjet5 = builder.get_object('spindurhot'); - this.settings.bind(DURHOTISHOT, widjet5, 'value', Gio.SettingsBindFlags.DEFAULT); - - // switch btn notif - let widjet6 = builder.get_object('swNotif'); - this.settings.bind(OKFORNOTIF, widjet6, 'active', Gio.SettingsBindFlags.DEFAULT); - - //feed sources - this.feedstore = builder.get_object('liststore1'); - this.loadStoreFromsettings( ); - - this.widget2 = builder.get_object('trvFeed'); - let column = builder.get_object('treeviewcolumn1'); - let cell = new Gtk.CellRendererText({ editable: false }); - column.pack_start(cell, true); - column.add_attribute(cell, "text", COLUMN_ID); - this.widget2.append_column(column); - - let delButton = builder.get_object('delButton'); - delButton.connect('clicked', ()=>{this.deleteSelected();}); - - let editButton = builder.get_object('editButton'); - editButton.connect('clicked', ()=>{this.editSelected();}); - - let newButton = builder.get_object('newButton'); - newButton.connect('clicked',()=>{this.createNew();}); - - return builder.get_object('prefs-container') ; - } - - /* Creates modal dialog new or editing - * title - dialog title - * text - text in dialog - * onOkButton - callback on OK button clicked */ - createDialog(title, text, onOkButton) { - let dialog = new Gtk.Dialog({title: title}); - dialog.set_modal(true); - dialog.set_resizable(true); - - let _entry = new Gtk.Entry({text: text}); - _entry.margin_bottom = 12; - _entry.width_chars = 80; - _entry.activates_default = true; - - _entry.connect("changed", ()=> { - if (_entry.get_text().length === 0) - _okButton.sensitive = false; - else - _okButton.sensitive = true; - }); - dialog.add_action_widget(_entry,2); - - //dialog.add_action_widget(new Gtk.Button({label:'Return',icon_name :'gtk-cancel'}) , 0); - dialog.add_action_widget(new Gtk.Button({label:'Return'}) , 0); - //let _okButton =new Gtk.Button({label:'OK',icon_name:'gtk-ok'}) ; - let _okButton =new Gtk.Button({label:'OK'}) ; - dialog.add_action_widget(_okButton , 1); - dialog.set_default_response(1); - - dialog.connect("response", (w, response_id)=> { - if (response_id) { // button OK - onOkButton(_entry.get_text()); - } - dialog.hide(); - }); - dialog.show(); - } - - createNew() { - this.createDialog(_("New Feed source"), '', (egtxt) =>{ - if (egtxt==''){ - return; - } - // update tree view - let iter = this.feedstore.append(); - this.feedstore.set_value(iter, COLUMN_ID, egtxt); - - // update this.settings - let feeds = this.settings.get_strv(RSS_FEEDS_LIST_KEY); - if (feeds == null) - feeds = new Array(); - - feeds.push(egtxt); - this.settings.set_strv(RSS_FEEDS_LIST_KEY, feeds); - this.settings.set_boolean("torefresh", true); - }); - } - - editSelected() {// update tree view - let [any, model, iter] = this.widget2.get_selection().get_selected(); - - if (any) { - this.createDialog(_("Edit Feed source"), model.get_value(iter, COLUMN_ID), (egtxt) =>{ - if (egtxt==''){ - return; - } - this.feedstore.set_value(iter, COLUMN_ID, egtxt); - - // update this.settings - let index = model.get_path(iter).get_indices(); - let feeds = this.settings.get_strv(RSS_FEEDS_LIST_KEY); - if (feeds == null) - feeds = new Array(); - - if (index < feeds.length) { - feeds[index] = egtxt; - this.settings.set_strv(RSS_FEEDS_LIST_KEY, feeds); - } - this.settings.set_boolean("torefresh", true); + // Update interval + const updateRow = new Adw.ActionRow({ + title: _('Update interval (minutes)'), + subtitle: _('0 = manual only'), + }); + const updateSpin = new Gtk.SpinButton({ + adjustment: new Gtk.Adjustment({lower: 0, upper: 1440, step_increment: 1, page_increment: 10}), + valign: Gtk.Align.CENTER, + }); + settings.bind(UPDATE_INTERVAL_KEY, updateSpin, 'value', Gio.SettingsBindFlags.DEFAULT); + updateRow.add_suffix(updateSpin); + timingGroup.add(updateRow); + + // Waiting delay for response + const rxDlyRow = new Adw.ActionRow({ + title: _('Waiting delay for response (seconds)'), + }); + const rxDlySpin = new Gtk.SpinButton({ + adjustment: new Gtk.Adjustment({lower: 1, upper: 360, step_increment: 1, page_increment: 10}), + valign: Gtk.Align.CENTER, + }); + settings.bind(DLYFORRX, rxDlySpin, 'value', Gio.SettingsBindFlags.DEFAULT); + rxDlyRow.add_suffix(rxDlySpin); + timingGroup.add(rxDlyRow); + + // --- Group: Display --- + const displayGroup = new Adw.PreferencesGroup({ + title: _('Display'), + }); + page.add(displayGroup); + + // Items visible per page + const itemsRow = new Adw.ActionRow({ + title: _('RSS sources per page'), + }); + const itemsSpin = new Gtk.SpinButton({ + adjustment: new Gtk.Adjustment({lower: 1, upper: 50, step_increment: 1, page_increment: 10}), + valign: Gtk.Align.CENTER, + }); + settings.bind(ITEMS_VISIBLE_KEY, itemsSpin, 'value', Gio.SettingsBindFlags.DEFAULT); + itemsRow.add_suffix(itemsSpin); + displayGroup.add(itemsRow); + + // Erase after (hours) + const eraseRow = new Adw.ActionRow({ + title: _('Erase after (hours)'), + }); + const eraseSpin = new Gtk.SpinButton({ + adjustment: new Gtk.Adjustment({lower: 1, upper: 100, step_increment: 1, page_increment: 10}), + valign: Gtk.Align.CENTER, + }); + settings.bind(DELETE_AFTER, eraseSpin, 'value', Gio.SettingsBindFlags.DEFAULT); + eraseRow.add_suffix(eraseSpin); + displayGroup.add(eraseRow); + + // --- Group: Notifications --- + const notifGroup = new Adw.PreferencesGroup({ + title: _('Notifications'), + }); + page.add(notifGroup); + + // Duration of news (minutes) + const durRow = new Adw.ActionRow({ + title: _('Duration of news (minutes)'), + }); + const durSpin = new Gtk.SpinButton({ + adjustment: new Gtk.Adjustment({lower: 1, upper: 360, step_increment: 1, page_increment: 10}), + valign: Gtk.Align.CENTER, + }); + settings.bind(DURHOTISHOT, durSpin, 'value', Gio.SettingsBindFlags.DEFAULT); + durRow.add_suffix(durSpin); + notifGroup.add(durRow); + + // Notification switch + const notifRow = new Adw.ActionRow({ + title: _('Notification on news'), + }); + const notifSwitch = new Gtk.Switch({ + valign: Gtk.Align.CENTER, + }); + settings.bind(OKFORNOTIF, notifSwitch, 'active', Gio.SettingsBindFlags.DEFAULT); + notifRow.add_suffix(notifSwitch); + notifGroup.add(notifRow); + + // --- Page: Feeds --- + const feedsPage = new Adw.PreferencesPage({ + title: _('Feeds'), + icon_name: 'application-rss+xml-symbolic', + }); + window.add(feedsPage); + + const feedsGroup = new Adw.PreferencesGroup({ + title: _('Feed sources'), + }); + feedsPage.add(feedsGroup); + + // Use Gtk.ListBox for reliable add/remove of feed rows + // (Adw.PreferencesGroup.remove() does not work for dynamic rows) + const listBox = new Gtk.ListBox({ + selection_mode: Gtk.SelectionMode.NONE, + css_classes: ['boxed-list'], + }); + feedsGroup.add(listBox); + + // Rebuild feed list from settings; remove_all() clears the ListBox + const _rebuildFeedRows = () => { + listBox.remove_all(); + + const feeds = settings.get_strv(RSS_FEEDS_LIST_KEY); + for (let i = 0; i < feeds.length; i++) { + // activatable: false — prevents row from capturing button clicks + const row = new Adw.ActionRow({title: feeds[i], activatable: false}); + + const editBtn = new Gtk.Button({ + icon_name: 'document-edit-symbolic', + valign: Gtk.Align.CENTER, + css_classes: ['flat'], }); + editBtn.connect('clicked', () => { + this._showEditDialog(window, settings, i, _rebuildFeedRows); + }); + + const delBtn = new Gtk.Button({ + icon_name: 'user-trash-symbolic', + valign: Gtk.Align.CENTER, + css_classes: ['flat'], + }); + delBtn.connect('clicked', () => { + let f = settings.get_strv(RSS_FEEDS_LIST_KEY); + f.splice(i, 1); + settings.set_strv(RSS_FEEDS_LIST_KEY, f); + _rebuildFeedRows(); + }); + + row.add_suffix(editBtn); + row.add_suffix(delBtn); + listBox.append(row); } - } - - deleteSelected() { - let [any, model, iter] = this.widget2.get_selection().get_selected(); - if (any) { - let index = model.get_path(iter).get_indices(); - this.feedstore.remove(iter); - // update this.settings - let feeds = this.settings.get_strv(RSS_FEEDS_LIST_KEY); - if (feeds == null) - feeds = new Array(); - - if (index < feeds.length) { - feeds.splice(index, 1); - this.settings.set_strv(RSS_FEEDS_LIST_KEY, feeds); - } - } - } - - loadStoreFromsettings() { - let feeds = this.settings.get_strv(RSS_FEEDS_LIST_KEY); - if (feeds) { - for (let i = 0; i < feeds.length; i++) { - if (feeds[i]) { // test on empty string - let iter = this.feedstore.append(); - this.feedstore.set_value(iter, COLUMN_ID, feeds[i]); - } - } + }; + + _rebuildFeedRows(); + + // Add button + const addGroup = new Adw.PreferencesGroup(); + const addBtn = new Gtk.Button({ + label: _('Add Feed'), + icon_name: 'list-add-symbolic', + css_classes: ['suggested-action'], + halign: Gtk.Align.CENTER, + }); + addBtn.connect('clicked', () => { + this._showAddDialog(window, settings, _rebuildFeedRows); + }); + addGroup.add(addBtn); + feedsPage.add(addGroup); + } + + // Modal dialog for adding a new feed URL + _showAddDialog(parentWindow, settings, rebuild) { + const dialog = new Adw.MessageDialog({ + transient_for: parentWindow, + heading: _('New Feed source'), + body: _('Enter feed URL:'), + }); + + const entry = new Gtk.Entry({ + placeholder_text: 'https://example.com/feed.xml', + width_chars: 50, + }); + dialog.set_extra_child(entry); + + dialog.add_response('cancel', _('Cancel')); + dialog.add_response('add', _('Add')); + dialog.set_response_appearance('add', Adw.ResponseAppearance.SUGGESTED); + dialog.set_default_response('add'); + dialog.set_close_response('cancel'); + + entry.connect('changed', () => { + dialog.set_response_enabled('add', entry.get_text().length > 0); + }); + dialog.set_response_enabled('add', false); + + // Save text before destroy() — GTK may invalidate entry on close + dialog.connect('response', (_dlg, response) => { + const text = entry.get_text(); + dialog.destroy(); + if (response === 'add' && text.length > 0) { + let feeds = settings.get_strv(RSS_FEEDS_LIST_KEY); + feeds.push(text); + settings.set_strv(RSS_FEEDS_LIST_KEY, feeds); + rebuild(); } - } + }); + + dialog.present(); + } + + // Modal dialog for editing an existing feed URL + _showEditDialog(parentWindow, settings, index, rebuild) { + const feeds = settings.get_strv(RSS_FEEDS_LIST_KEY); + const dialog = new Adw.MessageDialog({ + transient_for: parentWindow, + heading: _('Edit Feed source'), + body: _('Edit feed URL:'), + }); + const entry = new Gtk.Entry({ + text: feeds[index], + width_chars: 50, + }); + dialog.set_extra_child(entry); -}); - + dialog.add_response('cancel', _('Cancel')); + dialog.add_response('save', _('Save')); + dialog.set_response_appearance('save', Adw.ResponseAppearance.SUGGESTED); + dialog.set_default_response('save'); + dialog.set_close_response('cancel'); + entry.connect('changed', () => { + dialog.set_response_enabled('save', entry.get_text().length > 0); + }); + + // Save text before destroy() — GTK may invalidate entry on close + dialog.connect('response', (_dlg, response) => { + const text = entry.get_text(); + dialog.destroy(); + if (response === 'save' && text.length > 0) { + let f = settings.get_strv(RSS_FEEDS_LIST_KEY); + f[index] = text; + settings.set_strv(RSS_FEEDS_LIST_KEY, f); + rebuild(); + } + }); + + dialog.present(); + } +} diff --git a/webfeed@xynium.github.com/rss.js b/webfeed@xynium.github.com/rss.js index 82a9aa4..cab031e 100644 --- a/webfeed@xynium.github.com/rss.js +++ b/webfeed@xynium.github.com/rss.js @@ -4,7 +4,7 @@ */ -var RssParser=class RssParser { +export class RssParser { constructor(xmlBase){ this.Items =[]; this.Title ='';