Warning, /kdevelop/kdevelop/plugins/welcomepage/qml/NewsFeed.qml is written in an unsupported language. File is not indexed.
0001 /* 0002 SPDX-FileCopyrightText: 2017 Kevin Funk <kfunk@kde.org> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 import QtQuick 2.7 0008 import QtQuick.Controls 2.0 0009 import QtQuick.Layouts 1.2 0010 import QtQuick.XmlListModel 2.0 0011 0012 import org.kde.kdevplatform 1.0 0013 0014 import "storage.js" as Storage 0015 0016 ListView { 0017 id: root 0018 0019 /// Update interval (in seconds) in which the news feed is polled 0020 property int updateInterval: 24 * 3600 // 24 hours 0021 /// Max age (in seconds) of a news entry so it is shown in the list view 0022 /// TODO: Implement me 0023 property int maxNewsAge: 3 * 30 * 24 * 3600 // 3 months 0024 /// Max age (in seconds) of a news entry so it is considered 'new' (thus highlighted with a bold font) 0025 property int maxHighlightedNewsAge: 30 * 24 * 3600 // a month 0026 0027 readonly property string feedUrl: "https://www.kdevelop.org/news/feed" 0028 readonly property bool loading: newsFeedSyncModel.status === XmlListModel.Loading 0029 0030 /// Returns a date parsed from the pubDate 0031 function parsePubDate(pubDate) { 0032 // We need to modify the pubDate read from the RSS feed 0033 // so the JavaScript Date object can interpret it 0034 var d = pubDate.replace(',','').split(' '); 0035 if (d.length != 6) 0036 return new Date(NaN); 0037 0038 return new Date([d[0], d[2], d[1], d[3], d[4], 'GMT' + d[5]].join(' ')); 0039 } 0040 0041 // there's no builtin function for this(?) 0042 function toMap(obj) { 0043 var map = {}; 0044 for (var k in obj) { 0045 map[k] = obj[k]; 0046 } 0047 return map; 0048 } 0049 0050 function secondsSince(date) { 0051 return !isNaN(date) ? Math.floor(Number((new Date() - date)) / 1000) : -1; 0052 } 0053 0054 function loadEntriesFromCache() { 0055 newsFeedOfflineModel.clear() 0056 0057 var data = Storage.get("newsFeedOfflineModelData", null); 0058 if (data) { 0059 var newsEntries = JSON.parse(data); 0060 for (var i = 0; i < newsEntries.length; ++i) { 0061 newsFeedOfflineModel.append(newsEntries[i]); 0062 } 0063 } 0064 root.positionViewAtBeginning() 0065 } 0066 function saveEntriesToCache() { 0067 var newsEntries = []; 0068 for (var i = 0; i < newsFeedSyncModel.count; ++i) { 0069 var entry = newsFeedSyncModel.get(i); 0070 newsEntries.push(toMap(entry)); 0071 } 0072 Storage.set("newsFeedOfflineModelData", JSON.stringify(newsEntries)); 0073 } 0074 0075 spacing: 10 0076 0077 // Note: this model is *not* attached to the view -- it's merely used for fetching the RSS feed 0078 XmlListModel { 0079 id: newsFeedSyncModel 0080 0081 property bool active: false 0082 0083 source: active ? feedUrl : "" 0084 query: "/rss/channel/item" 0085 0086 XmlRole { name: "title"; query: "title/string()" } 0087 XmlRole { name: "link"; query: "link/string()" } 0088 XmlRole { name: "pubDate"; query: "pubDate/string()" } 0089 0090 onStatusChanged: { 0091 if (status == XmlListModel.Ready) { 0092 saveEntriesToCache(); 0093 loadEntriesFromCache(); 0094 0095 Storage.set("newsFeedLastFetchDate", JSON.stringify(new Date())); 0096 } else if (status == XmlListModel.Error) { 0097 Logger.log("Failed to fetch news feed: " + errorString()); 0098 } 0099 } 0100 } 0101 0102 ListModel { 0103 id: newsFeedOfflineModel 0104 } 0105 0106 // detach from model while fetching feed to make space for the busy indicator 0107 model: root.loading ? undefined : newsFeedOfflineModel 0108 0109 delegate: Column { 0110 id: feedDelegate 0111 0112 readonly property date publicationDate: parsePubDate(model.pubDate) 0113 /// in seconds 0114 readonly property int age: secondsSince(publicationDate) 0115 readonly property bool isNew: age != -1 && age < maxHighlightedNewsAge 0116 readonly property string dateString: isNaN(publicationDate.getDate()) ? model.pubDate : publicationDate.toLocaleDateString() 0117 0118 x: 10 0119 width: parent.width - 2*x 0120 0121 Link { 0122 width: parent.width 0123 0124 text: model.title 0125 0126 onClicked: Qt.openUrlExternally(model.link) 0127 } 0128 0129 Label { 0130 width: parent.width 0131 0132 font.bold: isNew 0133 font.pointSize: 8 0134 color: disabledPalette.windowText 0135 0136 text: isNew ? i18nc("Example: Tue, 03 Jan 2017 10:00:00 (new)", "%1 (new)", dateString) : dateString 0137 } 0138 } 0139 0140 Label { 0141 id: placeHolderLabel 0142 0143 x: 10 0144 width: parent.width - 2*x 0145 0146 text: root.loading ? 0147 i18n("Fetching feeds...") : 0148 i18n("No recent news") 0149 color: disabledPalette.windowText 0150 visible: root.count === 0 0151 0152 Behavior on opacity { NumberAnimation {} } 0153 } 0154 0155 SystemPalette { 0156 id: disabledPalette 0157 colorGroup: SystemPalette.Disabled 0158 } 0159 0160 function fetchFeed() { 0161 Logger.log("Fetching news feed") 0162 0163 newsFeedSyncModel.active = true 0164 newsFeedSyncModel.reload() 0165 } 0166 0167 Timer { 0168 id: delayedStartupTimer 0169 0170 // delay loading a bit so it has no effect on the KDevelop startup 0171 interval: 3000 0172 running: true 0173 0174 onTriggered: { 0175 // only fetch feed if items are out of date 0176 var lastFetchDate = new Date(JSON.parse(Storage.get("newsFeedLastFetchDate", null))); 0177 Logger.log("Last fetch of news feed was on " + lastFetchDate); 0178 if (secondsSince(lastFetchDate) > root.updateInterval) { 0179 root.fetchFeed(); 0180 } 0181 } 0182 } 0183 0184 Timer { 0185 id: reloadFeedTimer 0186 0187 interval: root.updateInterval * 1000 0188 running: true 0189 repeat: true 0190 0191 onTriggered: root.fetchFeed() 0192 } 0193 0194 Component.onCompleted: loadEntriesFromCache() 0195 }