diff --git a/src/freenet/clients/http/UserAlertsToadlet.java b/src/freenet/clients/http/UserAlertsToadlet.java index 57fce3ab3fe..06ecf6c6f51 100644 --- a/src/freenet/clients/http/UserAlertsToadlet.java +++ b/src/freenet/clients/http/UserAlertsToadlet.java @@ -11,6 +11,9 @@ import freenet.client.HighLevelSimpleClient; import freenet.l10n.NodeL10n; +import freenet.node.useralerts.AbstractNodeToNodeFileOfferUserAlert; +import freenet.node.useralerts.NodeToNodeMessageUserAlert; +import freenet.node.useralerts.UserAlert; import freenet.support.HTMLNode; import freenet.support.MultiValueTable; import freenet.support.api.HTTPRequest; @@ -21,13 +24,17 @@ */ public class UserAlertsToadlet extends Toadlet { + private static final String DISMISS_ALL_ALERTS_PART = "dismissAllAlerts"; + private static final String DELETE_ALL_MESSAGES_PART = "deleteAllMessages"; + private static final String REALLY_DELETE_AFFIRMED = "reallyDeleteAffirmed"; + UserAlertsToadlet(HighLevelSimpleClient client) { super(client); } public void handleMethodGET(URI uri, HTTPRequest request, ToadletContext ctx) throws ToadletContextClosedException, IOException { - if(!ctx.checkFullAccess(this)) - return; + if(!ctx.checkFullAccess(this)) + return; PageNode page = ctx.getPageMaker().getPageNode(l10n("title"), ctx); HTMLNode pageNode = page.outer; @@ -36,18 +43,64 @@ public void handleMethodGET(URI uri, HTTPRequest request, ToadletContext ctx) th if (alertsNode.getFirstTag() == null) { alertsNode = new HTMLNode("div", "class", "infobox"); alertsNode.addChild("div", "class", "infobox-content").addChild("div", NodeL10n.getBase().getString("UserAlertsToadlet.noMessages")); + } else { + addDismissAllButtons(ctx, contentNode); } contentNode.addChild(alertsNode); writeHTMLReply(ctx, 200, "OK", pageNode.generate()); } + private void addDismissAllButtons(ToadletContext ctx, HTMLNode contentNode) { + HTMLNode dismissAlertsContainer = contentNode.addChild("div", "class", "dismiss-all-alerts-container"); + HTMLNode dismissAlertsForm = ctx.addFormChild(dismissAlertsContainer, "", "deleteAllNotificationsForm"); + dismissAlertsForm.addChild("input", + new String[]{"name", "type", "title", "value"}, + new String[]{ DISMISS_ALL_ALERTS_PART, "submit", l10n("dismissAlertsButtonTitle"), l10n("dismissAlertsButtonContent")}); + HTMLNode deleteMessagesForm = ctx.addFormChild(dismissAlertsContainer, "", "deleteAllNotificationsForm"); + deleteMessagesForm.addChild("input", + new String[]{"name", "type", "title", "value"}, + new String[]{ DELETE_ALL_MESSAGES_PART, "submit", l10n("deleteMessagesButtonTitle"), l10n("deleteMessagesButtonContent")}); + String deleteMessagesReallyCheckboxId = "reallyDeleteAllMessagesCheckbox"; + deleteMessagesForm.addChild( + "input", + new String[]{ "type", "name", "id", "required" }, + new String[]{ "checkbox", REALLY_DELETE_AFFIRMED, deleteMessagesReallyCheckboxId, "true" }); + deleteMessagesForm.addChild("label", "for", deleteMessagesReallyCheckboxId, l10n("deleteMessagesButtonReallyDeleteLabel")); + } + public void handleMethodPOST(URI uri, HTTPRequest request, ToadletContext ctx) throws ToadletContextClosedException, IOException { if (request.isPartSet("dismiss-user-alert")) { int userAlertHashCode = request.getIntPart("disable", -1); ctx.getAlertManager().dismissAlert(userAlertHashCode); } - + if (request.isPartSet(DISMISS_ALL_ALERTS_PART)) { + for (UserAlert alert : ctx.getAlertManager().getAlerts()) { + if (!alert.userCanDismiss()) { + continue; + } + if (alert instanceof NodeToNodeMessageUserAlert) { + continue; // keep all node to node messages + } + ctx.getAlertManager().dismissAlert(alert.hashCode()); + } + } + + if (request.isPartSet(DELETE_ALL_MESSAGES_PART) && request.isPartSet(REALLY_DELETE_AFFIRMED)) { + for (UserAlert alert : ctx.getAlertManager().getAlerts()) { + if (!(alert instanceof NodeToNodeMessageUserAlert)) { + continue; + } + if (alert instanceof AbstractNodeToNodeFileOfferUserAlert) { + continue; // not deleting file offers, because these need a decision + } + if (!alert.userCanDismiss()) { + continue; + } + ctx.getAlertManager().dismissAlert(alert.hashCode()); + } + } + String redirect; try { redirect = request.getPartAsStringThrowing("redirectToAfterDisable", 1024); @@ -57,9 +110,9 @@ public void handleMethodPOST(URI uri, HTTPRequest request, ToadletContext ctx) t // hard whitelist of allowed origins to avoid https://www.owasp.org/index.php/Unvalidated_Redirects_and_Forwards_Cheat_Sheet // TODO: Parse the URL to ensure that it is a valid fproxy URL if (!("/alerts/".equals(redirect) || - "/".equals(redirect) || - "/#bookmarks".equals(redirect))) { - redirect = "."; + "/".equals(redirect) || + "/#bookmarks".equals(redirect))) { + redirect = "."; } MultiValueTable headers = MultiValueTable.from("Location", redirect); ctx.sendReplyHeaders(302, "Found", headers, null, 0); diff --git a/src/freenet/clients/http/staticfiles/baseelements.css b/src/freenet/clients/http/staticfiles/baseelements.css index 5c2215ff7c8..429b5a60e05 100644 --- a/src/freenet/clients/http/staticfiles/baseelements.css +++ b/src/freenet/clients/http/staticfiles/baseelements.css @@ -592,6 +592,16 @@ div.n2ntm-message-text { font-weight: bold; } +/* + * Alerts Page + */ + +div.dismiss-all-alerts-container { + display: flex; + align-items: center; + gap: 0.6em; +} + /* * Translation */ diff --git a/src/freenet/clients/http/staticfiles/themes/minimalblue/theme.css b/src/freenet/clients/http/staticfiles/themes/minimalblue/theme.css index 4d59547f4fd..7d50a26907e 100644 --- a/src/freenet/clients/http/staticfiles/themes/minimalblue/theme.css +++ b/src/freenet/clients/http/staticfiles/themes/minimalblue/theme.css @@ -558,6 +558,18 @@ span.peer_idle_old { font-weight: bold; } + +/* + * Alerts Page + */ + +div.dismiss-all-alerts-container { + display: flex; + align-items: center; + gap: 0.6em; + margin-left: 5px; +} + /* statusbar */ div#statusbar-container { @@ -620,4 +632,4 @@ div#statusbar div#statusbar-alerts.contains-information, div#statusbar div#statu .hidden ol li, .hidden ol { list-style: decimal outside none; margin-left: 0px; -} \ No newline at end of file +} diff --git a/src/freenet/clients/http/staticfiles/themes/minimalist/theme.css b/src/freenet/clients/http/staticfiles/themes/minimalist/theme.css index 453953c0e8d..c1d4a86ecf5 100644 --- a/src/freenet/clients/http/staticfiles/themes/minimalist/theme.css +++ b/src/freenet/clients/http/staticfiles/themes/minimalist/theme.css @@ -254,6 +254,17 @@ div#content div.infobox.infobox-summary-status-box div.infobox-content li { margin-right: 5px; } +/* + * Alerts Page + */ + +div.dismiss-all-alerts-container { + display: flex; + align-items: center; + gap: 0.6em; +} + + /* ***************************************************************** * statusbar settings * *****************************************************************/ diff --git a/src/freenet/clients/http/staticfiles/themes/rabbit-hole/theme.css b/src/freenet/clients/http/staticfiles/themes/rabbit-hole/theme.css index dd32d0110ae..6ee53249e7b 100644 --- a/src/freenet/clients/http/staticfiles/themes/rabbit-hole/theme.css +++ b/src/freenet/clients/http/staticfiles/themes/rabbit-hole/theme.css @@ -99,6 +99,16 @@ li.alert-summary-text-minor { background: url(information-16.png) left center no-repeat; } +/* + * Alerts Page + */ + +div.dismiss-all-alerts-container { + display: flex; + align-items: center; + gap: 0.6em; + margin: 5px 10px; +} /* statusbar */ @@ -841,4 +851,4 @@ div.infobox div.infobox-content { #selected-subnavbar { display: none -} \ No newline at end of file +} diff --git a/src/freenet/clients/http/staticfiles/themes/winterfacey/theme.css b/src/freenet/clients/http/staticfiles/themes/winterfacey/theme.css index 31f18998586..2702b080e7b 100644 --- a/src/freenet/clients/http/staticfiles/themes/winterfacey/theme.css +++ b/src/freenet/clients/http/staticfiles/themes/winterfacey/theme.css @@ -4,6 +4,13 @@ ul#navlist { } +/*** From baseelements.css ***/ +div.dismiss-all-alerts-container { + display: flex; + align-items: center; + gap: 0.6em; +} + /*** From base.css ***/ #statusbar-container { position: fixed; diff --git a/src/freenet/l10n/freenet.l10n.en.properties b/src/freenet/l10n/freenet.l10n.en.properties index 9e3b11a52c5..06f3a6c0ca7 100644 --- a/src/freenet/l10n/freenet.l10n.en.properties +++ b/src/freenet/l10n/freenet.l10n.en.properties @@ -2246,6 +2246,11 @@ UserAlertManager.totalLabel=Total: UserAlertManager.warningCountLabel=Warnings: UserAlertsToadlet.title=Status messages UserAlertsToadlet.noMessages=No messages +UserAlertsToadlet.dismissAlertsButtonContent=Dismiss All Alerts +UserAlertsToadlet.dismissAlertsButtonTitle=Dismiss alerts? Will not delete messages from Friends. +UserAlertsToadlet.deleteMessagesButtonContent=Delete All Messages +UserAlertsToadlet.deleteMessagesButtonTitle=Delete messages from Friends except for file offerse? Will not delete other alerts. +UserAlertsToadlet.deleteMessagesButtonReallyDeleteLabel=Really Delete? VorbisBitstreamFilter.MalformedTitle=Malformed Vorbis Bitstream VorbisBitstreamFilter.MalformedMessage=The Vorbis bitstream is not correctly formatted, and could not be properly validated. WebPFilter.animUnsupportedTitle=WebP animation is currently not supported diff --git a/src/freenet/node/DarknetPeerNode.java b/src/freenet/node/DarknetPeerNode.java index 462fd549e0a..f7de2944021 100644 --- a/src/freenet/node/DarknetPeerNode.java +++ b/src/freenet/node/DarknetPeerNode.java @@ -14,6 +14,7 @@ import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; +import java.lang.ref.WeakReference; import java.net.MalformedURLException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -39,6 +40,7 @@ import freenet.io.xfer.PartiallyReceivedBulk; import freenet.keys.FreenetURI; import freenet.l10n.NodeL10n; +import freenet.node.useralerts.AbstractNodeToNodeFileOfferUserAlert; import freenet.node.useralerts.AbstractUserAlert; import freenet.node.useralerts.BookmarkFeedUserAlert; import freenet.node.useralerts.DownloadFeedUserAlert; @@ -1019,7 +1021,7 @@ public void onRejected() { } protected void onReceiveFailure() { - UserAlert alert = new AbstractUserAlert() { + UserAlert alert = new AbstractNodeToNodeFileOfferUserAlert() { @Override public String dismissButtonText() { return NodeL10n.getBase().getString("UserAlert.hide"); @@ -1112,7 +1114,7 @@ public String getShortText() { } private void onReceiveSuccess() { - UserAlert alert = new AbstractUserAlert() { + UserAlert alert = new AbstractNodeToNodeFileOfferUserAlert() { @Override public String dismissButtonText() { return NodeL10n.getBase().getString("UserAlert.hide"); @@ -1185,7 +1187,8 @@ public String getShortText() { /** Ask the user whether (s)he wants to download a file from a direct peer */ public UserAlert askUserUserAlert() { - return new AbstractUserAlert() { + return new AbstractNodeToNodeFileOfferUserAlert() { + @Override public String dismissButtonText() { return null; // Cannot hide, but can reject diff --git a/src/freenet/node/useralerts/AbstractNodeToNodeFileOfferUserAlert.java b/src/freenet/node/useralerts/AbstractNodeToNodeFileOfferUserAlert.java new file mode 100644 index 00000000000..1f110fed82a --- /dev/null +++ b/src/freenet/node/useralerts/AbstractNodeToNodeFileOfferUserAlert.java @@ -0,0 +1,10 @@ +package freenet.node.useralerts; + +import freenet.support.HTMLNode; + +/** + * Helper to create anonymous classes with interface NodeToNodeMessageUserAlert + */ +public abstract class AbstractNodeToNodeFileOfferUserAlert extends AbstractUserAlert implements NodeToNodeMessageUserAlert { + // intentionally left blank +} diff --git a/src/freenet/node/useralerts/BookmarkFeedUserAlert.java b/src/freenet/node/useralerts/BookmarkFeedUserAlert.java index d782cd682d1..e19fc9669f9 100644 --- a/src/freenet/node/useralerts/BookmarkFeedUserAlert.java +++ b/src/freenet/node/useralerts/BookmarkFeedUserAlert.java @@ -9,7 +9,7 @@ import freenet.node.PeerNode; import freenet.support.HTMLNode; -public class BookmarkFeedUserAlert extends AbstractUserAlert { +public class BookmarkFeedUserAlert extends AbstractUserAlert implements NodeToNodeMessageUserAlert { private final WeakReference peerRef; private final FreenetURI uri; private final int fileNumber; @@ -33,8 +33,8 @@ public BookmarkFeedUserAlert(DarknetPeerNode sourcePeerNode, this.composed = composed; this.sent = sent; this.received = received; - peerRef = sourcePeerNode.getWeakRef(); - sourceNodeName = sourcePeerNode.getName(); + this.peerRef = sourcePeerNode.getWeakRef(); + this.sourceNodeName = sourcePeerNode.getName(); } @Override @@ -115,4 +115,5 @@ public boolean isValid() { sourceNodeName = pn.getName(); return true; } + } diff --git a/src/freenet/node/useralerts/DownloadFeedUserAlert.java b/src/freenet/node/useralerts/DownloadFeedUserAlert.java index 5ea6611ea57..567a808d9c2 100644 --- a/src/freenet/node/useralerts/DownloadFeedUserAlert.java +++ b/src/freenet/node/useralerts/DownloadFeedUserAlert.java @@ -10,7 +10,7 @@ import freenet.node.PeerNode; import freenet.support.HTMLNode; -public class DownloadFeedUserAlert extends AbstractUserAlert { +public class DownloadFeedUserAlert extends AbstractUserAlert implements NodeToNodeMessageUserAlert { private final WeakReference peerRef; private final FreenetURI uri; private final int fileNumber; @@ -29,8 +29,8 @@ public DownloadFeedUserAlert(DarknetPeerNode sourcePeerNode, this.composed = composed; this.sent = sent; this.received = received; - peerRef = sourcePeerNode.getWeakRef(); - sourceNodeName = sourcePeerNode.getName(); + this.peerRef = sourcePeerNode.getWeakRef(); + this.sourceNodeName = sourcePeerNode.getName(); } @Override @@ -104,4 +104,5 @@ public boolean isValid() { sourceNodeName = pn.getName(); return true; } + } diff --git a/src/freenet/node/useralerts/N2NTMUserAlert.java b/src/freenet/node/useralerts/N2NTMUserAlert.java index ebdb397ffe6..b2d60da3aa9 100644 --- a/src/freenet/node/useralerts/N2NTMUserAlert.java +++ b/src/freenet/node/useralerts/N2NTMUserAlert.java @@ -15,7 +15,7 @@ import freenet.support.HTMLNode; // Node To Node Text Message User Alert -public class N2NTMUserAlert extends AbstractUserAlert { +public class N2NTMUserAlert extends AbstractUserAlert implements NodeToNodeMessageUserAlert { private final WeakReference peerRef; private final String messageText; private final int fileNumber; @@ -151,5 +151,4 @@ public boolean isValid() { } return true; } - } diff --git a/src/freenet/node/useralerts/NodeToNodeMessageUserAlert.java b/src/freenet/node/useralerts/NodeToNodeMessageUserAlert.java new file mode 100644 index 00000000000..d1b884d322d --- /dev/null +++ b/src/freenet/node/useralerts/NodeToNodeMessageUserAlert.java @@ -0,0 +1,12 @@ +package freenet.node.useralerts; + +import java.lang.ref.WeakReference; + +import freenet.node.PeerNode; +import freenet.support.HTMLNode; + +/** + * Tagging interface for user alerts that are node to node messages. + */ +public interface NodeToNodeMessageUserAlert { +}