From c3ad1d3691879c3f121f36003ec18b7e72997304 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Thu, 30 Mar 2017 12:40:57 +0200 Subject: [PATCH] Announcements: Various improvements * Added combined OctoBlog feed, replacing news + spotlight (+ octoprintonair), added corresponding config migration * Subscribe to all registered feeds by default * Added config button to announcement reader * Added note how to edit announcement subscriptions to notifications * Auto-hide announcements on logout * Order channels server-side based on new order config setting --- .../plugins/announcements/__init__.py | 111 ++++++++++++------ .../static/css/announcements.css | 2 +- .../announcements/static/js/announcements.js | 28 ++++- .../static/less/announcements.less | 6 + .../templates/announcements.jinja2 | 5 +- .../templates/announcements_settings.jinja2 | 3 +- 6 files changed, 111 insertions(+), 44 deletions(-) diff --git a/src/octoprint/plugins/announcements/__init__.py b/src/octoprint/plugins/announcements/__init__.py index 0ae7d9a5..f65843cb 100644 --- a/src/octoprint/plugins/announcements/__init__.py +++ b/src/octoprint/plugins/announcements/__init__.py @@ -18,6 +18,8 @@ import threading import feedparser import flask +from collections import OrderedDict + from octoprint.server import admin_permission from octoprint.server.util.flask import restricted_access, with_revalidation_checking, check_etag from flask.ext.babel import gettext @@ -44,35 +46,64 @@ class AnnouncementPlugin(octoprint.plugin.AssetPlugin, # SettingsPlugin def get_settings_defaults(self): - return dict(channels=dict(_important=dict(name="Important OctoPrint Announcements", - priority=1, - type="rss", - url="http://octoprint.org/feeds/important.xml"), - _news=dict(name="OctoPrint News", - priority=2, - type="rss", - url="http://octoprint.org/feeds/news.xml"), - _releases=dict(name="OctoPrint Release Announcements", + settings = dict(channels=dict(_important=dict(name="Important Announcements", + description="Important announcements about OctoPrint.", + priority=1, + type="rss", + url="http://octoprint.org/feeds/important.xml"), + _releases=dict(name="Release Announcements", + description="Announcements of new releases and release candidates of OctoPrint.", + priority=2, + type="rss", + url="http://octoprint.org/feeds/releases.xml"), + _blog=dict(name="On the OctoBlog", + description="Development news, community spotlights, OctoPrint On Air episodes and more from the official OctoBlog.", priority=2, type="rss", - url="http://octoprint.org/feeds/releases.xml"), - _spotlight=dict(name="OctoPrint Community Spotlights", - priority=2, - type="rss", - url="http://octoprint.org/feeds/spotlight.xml"), - _octopi=dict(name="OctoPi Announcements", - priority=2, - type="rss", - url="http://octoprint.org/feeds/octopi.xml"), - _plugins=dict(name="New Plugins in the Repository", - priority=2, - type="rss", - url="http://plugins.octoprint.org/feed.xml")), - enabled_channels=[], - forced_channels=["_important"], - ttl=6*60, - display_limit=3, - summary_limit=300) + url="http://octoprint.org/feeds/octoblog.xml"), + _plugins=dict(name="New Plugins in the Repository", + description="Announcements of new plugins released on the official Plugin Repository.", + priority=2, + type="rss", + url="http://plugins.octoprint.org/feed.xml"), + _octopi=dict(name="OctoPi News", + description="News around OctoPi, the Raspberry Pi image including OctoPrint.", + priority=2, + type="rss", + url="http://octoprint.org/feeds/octopi.xml")), + enabled_channels=[], + forced_channels=["_important"], + channel_order=["_important", "_releases", "_blog", "_plugins", "_octopi"], + ttl=6*60, + display_limit=3, + summary_limit=300) + settings["enabled_channels"] = settings["channels"].keys() + return settings + + def get_settings_version(self): + return 1 + + def on_settings_migrate(self, target, current): + if current is None: + # first version had different default feeds and only _important enabled by default + channels = self._settings.get(["channels"]) + if "_news" in channels: + del channels["_news"] + if "_spotlight" in channels: + del channels["_spotlight"] + self._settings.set(["channels"], channels) + + enabled = self._settings.get(["enabled_channels"]) + add_blog = False + if "_news" in enabled: + add_blog = True + enabled.remove("_news") + if "_spotlight" in enabled: + add_blog = True + enabled.remove("_spotlight") + if add_blog and not "_blog" in enabled: + enabled.append("_blog") + self._settings.set(["enabled_channels"], enabled) # AssetPlugin @@ -97,7 +128,7 @@ class AnnouncementPlugin(octoprint.plugin.AssetPlugin, def get_channel_data(self): from octoprint.settings import valid_boolean_trues - result = dict() + result = [] force = flask.request.values.get("force", "false") in valid_boolean_trues @@ -118,15 +149,17 @@ class AnnouncementPlugin(octoprint.plugin.AssetPlugin, last = entries[0]["published"] self._mark_read_until(key, last) - result[key] = dict(channel=data["name"], + result.append(dict(key=key, + channel=data["name"], url=data["url"], + description=data.get("description", ""), priority=data.get("priority", 2), enabled=key in enabled or key in forced, forced=key in forced, data=entries, - unread=unread) + unread=unread)) - return flask.jsonify(result) + return flask.jsonify(channels=result) def etag(): import hashlib @@ -141,11 +174,11 @@ class AnnouncementPlugin(octoprint.plugin.AssetPlugin, return hash.hexdigest() - def condition(): - return check_etag(etag()) + def condition(lm, etag): + return check_etag(etag) return with_revalidation_checking(etag_factory=lambda *args, **kwargs: etag(), - condition=lambda *args, **kwargs: condition(), + condition=lambda lm, etag: condition(lm, etag), unless=lambda: force)(view)() @octoprint.plugin.BlueprintPlugin.route("/channels/", methods=["POST"]) @@ -208,9 +241,13 @@ class AnnouncementPlugin(octoprint.plugin.AssetPlugin, with self._cached_channel_configs_mutex: if self._cached_channel_configs is None or force: configs = self._settings.get(["channels"], merged=True) - result = dict() - for key, config in configs.items(): - if "url" not in config or "name" not in config: + order = self._settings.get(["channel_order"]) + all_keys = order + [key for key in sorted(configs.keys()) if not key in order] + + result = OrderedDict() + for key in all_keys: + config = configs.get(key) + if config is None or "url" not in config or "name" not in config: # strip invalid entries continue result[self._slugify(key)] = config diff --git a/src/octoprint/plugins/announcements/static/css/announcements.css b/src/octoprint/plugins/announcements/static/css/announcements.css index 08e9a9bd..4df82b39 100644 --- a/src/octoprint/plugins/announcements/static/css/announcements.css +++ b/src/octoprint/plugins/announcements/static/css/announcements.css @@ -1 +1 @@ -table td.settings_plugin_announcements_channels_name,table th.settings_plugin_announcements_channels_name{text-overflow:ellipsis;text-align:left}table td.settings_plugin_announcements_channels_actions,table th.settings_plugin_announcements_channels_actions{text-align:center;width:80px}table td.settings_plugin_announcements_channels_actions a,table th.settings_plugin_announcements_channels_actions a{text-decoration:none;color:#000}table td.settings_plugin_announcements_channels_actions a.disabled,table th.settings_plugin_announcements_channels_actions a.disabled{color:#ccc;cursor:default}#plugin_announcements_dialog .unread{font-weight:700}#plugin_announcements_dialog article{padding-right:20px}#plugin_announcements_dialog article.read{opacity:.5}#plugin_announcements_dialog article.read:hover{opacity:1}#plugin_announcements_dialog article .actions{background-color:#f5f5f5;border-radius:2px;padding:2px 5px;margin-top:5px}#plugin_announcements_dialog article .actions .markread{float:right}#plugin_announcements_dialog article .actions a{color:#000} \ No newline at end of file +table td.settings_plugin_announcements_channels_name,table th.settings_plugin_announcements_channels_name{text-overflow:ellipsis;text-align:left}table td.settings_plugin_announcements_channels_actions,table th.settings_plugin_announcements_channels_actions{text-align:center;width:80px}table td.settings_plugin_announcements_channels_actions a,table th.settings_plugin_announcements_channels_actions a{text-decoration:none;color:#000}table td.settings_plugin_announcements_channels_actions a.disabled,table th.settings_plugin_announcements_channels_actions a.disabled{color:#ccc;cursor:default}#plugin_announcements_dialog .unread{font-weight:700}#plugin_announcements_dialog article{padding-right:20px}#plugin_announcements_dialog article.read{opacity:.5}#plugin_announcements_dialog article.read:hover{opacity:1}#plugin_announcements_dialog article .actions{background-color:#f5f5f5;border-radius:2px;padding:2px 5px;margin-top:5px}#plugin_announcements_dialog article .actions .markread{float:right}#plugin_announcements_dialog article .actions a{color:#000}#plugin_announcements_dialog .modal-footer .configurelink{float:left} \ No newline at end of file diff --git a/src/octoprint/plugins/announcements/static/js/announcements.js b/src/octoprint/plugins/announcements/static/js/announcements.js index 55c20815..dfd6d7d9 100644 --- a/src/octoprint/plugins/announcements/static/js/announcements.js +++ b/src/octoprint/plugins/announcements/static/js/announcements.js @@ -120,12 +120,13 @@ $(function() { }; self.fromResponse = function(data) { + if (!self.loginState.isAdmin()) return; + var currentTab = $("li.active a", self.announcementDialogTabs).attr("href"); var unread = 0; var channels = []; - _.each(data, function(value, key) { - value.key = key; + _.each(data.channels, function(value) { value.last = value.data.length ? value.data[0].published : undefined; value.count = value.data.length; unread += value.unread; @@ -140,6 +141,8 @@ $(function() { }; self.showAnnouncementDialog = function(channel) { + if (!self.loginState.isAdmin()) return; + self.announcementDialogContent.scrollTop(0); if (!self.announcementDialog.hasClass("in")) { @@ -172,6 +175,8 @@ $(function() { }; self.displayAnnouncements = function(channels) { + if (!self.loginState.isAdmin()) return; + var displayLimit = self.settings.settings.plugins.announcements.display_limit(); var maxLength = self.settings.settings.plugins.announcements.summary_limit(); @@ -222,7 +227,7 @@ $(function() { } var rest = newItems.length - displayedItems.length; - var text = "