From 0590cc6c1997273a6ed9382d451fbd565ed76179 Mon Sep 17 00:00:00 2001 From: Snowyfox Date: Wed, 27 Apr 2022 21:56:25 -0400 Subject: [PATCH] Added delete feature --- ComposeWindow.java | 2 +- MastodonApi.java | 21 +++++ NotificationsWindow.java | 59 ++++++++------ PostWindow.java | 160 ++++++++++++++++++++++++++++++++++++- RudimentaryHTMLParser.java | 20 ----- TimelineWindow.java | 38 +++++++-- 6 files changed, 246 insertions(+), 54 deletions(-) diff --git a/ComposeWindow.java b/ComposeWindow.java index a47036d..3a2dd7a 100755 --- a/ComposeWindow.java +++ b/ComposeWindow.java @@ -51,7 +51,7 @@ ComposeWindow extends JFrame { { composition = new Composition(); composition.text = ""; - composition.visibility = PostVisibility.MENTIONED; + composition.visibility = PostVisibility.UNLISTED; composition.replyToPostId = null; composition.contentWarning = null; syncDisplayToComposition(); diff --git a/MastodonApi.java b/MastodonApi.java index 6849cd5..d05b122 100644 --- a/MastodonApi.java +++ b/MastodonApi.java @@ -351,6 +351,27 @@ MastodonApi { catch (IOException eIo) { handler.connectionFailed(eIo); } } + public void + deletePost(String postId, RequestListener handler) + { + String token = accessToken.get("access_token").value; + + String url = instanceUrl + "/api/v1/statuses/" + postId; + try + { + URL endpoint = new URL(url); + HttpURLConnection conn; + conn = (HttpURLConnection)endpoint.openConnection(); + String s1 = "Bearer " + token; + conn.setRequestProperty("Authorization", s1); + conn.setRequestMethod("DELETE"); + conn.connect(); + + doStandardJsonReturn(conn, handler); + } + catch (IOException eIo) { handler.connectionFailed(eIo); } + } + public void getAccounts(String query, RequestListener handler) { diff --git a/NotificationsWindow.java b/NotificationsWindow.java index 635fc6d..838ad36 100644 --- a/NotificationsWindow.java +++ b/NotificationsWindow.java @@ -132,13 +132,14 @@ NotificationsWindow extends JFrame { if (n.type != NotificationType.FOLLOW) { Tree post = t.get("status"); - String pid = post.get("id").value; - String ptext = post.get("content").value; + String pid, phtml, ptext; + pid = post.get("id").value; + phtml = post.get("content").value; + ptext = + TimelineComponent + .textApproximation(phtml); n.postId = pid; n.postText = ptext; - // Should we ask TimelineWindow for help here? - // Or should we break our text parsers into - // a separate class? } notifications.add(n); @@ -162,7 +163,7 @@ NotificationsWindow extends JFrame { notifications = new ArrayList<>(); display = new NotificationsComponent(this); - display.setPreferredSize(new Dimension(256, 400)); + display.setPreferredSize(new Dimension(256, 260)); setContentPane(display); pack(); @@ -190,7 +191,7 @@ implements ActionListener { // - -%- - static final int - ROW_COUNT = 16; + ROW_COUNT = 10; // ---%-@-%--- @@ -203,7 +204,16 @@ implements ActionListener { Notification n = notifications.get(o); NotificationComponent c = rows.get(o); c.setName(n.actorName); - c.setType(n.type.toString()); + switch (n.type) + { + case MENTION: c.setType("mentioned"); break; + case BOOST: c.setType("boosted"); break; + case FAVOURITE: c.setType("favourited"); break; + case FOLLOW: c.setType("followed"); break; + case FOLLOWREQ: c.setType("req. follow"); break; + case POLL: c.setType("poll ended"); break; + case ALERT: c.setType("posted"); break; + } c.setText(n.postText); } } @@ -279,13 +289,13 @@ implements ComponentListener { componentResized(ComponentEvent eC) { int w = getWidth(), h = getHeight(); - name.setPreferredSize(new Dimension(w * 4 / 10, h)); - type.setPreferredSize(new Dimension(w * 3 / 10, h)); - text.setPreferredSize(new Dimension(w * 2 / 10, h)); + name.setPreferredSize(new Dimension(w * 7 / 20, h)); + type.setPreferredSize(new Dimension(w * 6 / 20, h)); + text.setPreferredSize(new Dimension(w * 5 / 20, h)); - name.setMaximumSize(new Dimension(w * 4 / 10, h)); - type.setMaximumSize(new Dimension(w * 3 / 10, h)); - text.setMaximumSize(new Dimension(w * 2 / 10, h)); + name.setMaximumSize(new Dimension(w * 7 / 20, h)); + type.setMaximumSize(new Dimension(w * 6 / 20, h)); + text.setMaximumSize(new Dimension(w * 5 / 20, h)); } public void @@ -301,9 +311,13 @@ implements ComponentListener { NotificationComponent() { - Font f1 = new Font("Dialog", Font.PLAIN, 12); - Font f2 = new Font("Dialog", Font.PLAIN, 10); - Font f3 = new Font("Dialog", Font.ITALIC, 12); + Font f = new Font("Dialog", Font.PLAIN, 12); + Font f1 = f.deriveFont(Font.PLAIN, 14); + Font f2 = f.deriveFont(Font.PLAIN, 11); + Font f3 = f.deriveFont(Font.ITALIC, 14); + + Color c = new Color(0, 0, 0, 25); + Border b1 = BorderFactory.createMatteBorder(0, 0, 1, 0, c); name = new JLabel(); type = new JLabel(); @@ -314,17 +328,16 @@ implements ComponentListener { type.setHorizontalAlignment(JLabel.RIGHT); setLayout(new BoxLayout(this, BoxLayout.X_AXIS)); + add(Box.createHorizontalStrut(4)); add(name); - add(Box.createHorizontalStrut(4)); + add(Box.createHorizontalStrut(8)); add(type); - add(Box.createHorizontalStrut(4)); + add(Box.createHorizontalStrut(8)); add(text); + add(Box.createHorizontalStrut(4)); this.addComponentListener(this); - setBorder( - BorderFactory.createMatteBorder - (1, 0, 0, 0, new Color(0, 0, 0, 25)) - ); + setBorder(b1); } } \ No newline at end of file diff --git a/PostWindow.java b/PostWindow.java index 98b7090..f82051f 100755 --- a/PostWindow.java +++ b/PostWindow.java @@ -2,7 +2,7 @@ import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JMenuBar; -import javax.swing.JMenu; +import javax.swing.JPopupMenu; import javax.swing.JMenuItem; import javax.swing.JButton; import javax.swing.JLabel; @@ -86,6 +86,10 @@ implements ActionListener { postDisplay.setAuthorName(an); postDisplay.setAuthorId(author.get("acct").value); + String aid = author.get("id").value; + String oid = api.getAccountDetails().get("id").value; + postDisplay.setDeleteEnabled(aid.equals(oid)); + String avurl = author.get("avatar").value; postDisplay.setAuthorAvatar(ImageApi.remote(avurl)); @@ -255,7 +259,7 @@ implements ActionListener { if (vs.equals("unlisted")) v = PostVisibility.UNLISTED; if (vs.equals("private")) v = PostVisibility.FOLLOWERS; if (vs.equals("direct")) v = PostVisibility.MENTIONED; - + Composition c = new Composition(); c.contentWarning = cw; c.text = id1.equals(id2) ? "" : "@" + authorId + " "; @@ -300,6 +304,112 @@ implements ActionListener { } } + public void + deletePost(boolean redraft) + { + postDisplay.setCursor(new Cursor(Cursor.WAIT_CURSOR)); + postDisplay.setDeleteEnabled(false); + postDisplay.paintImmediately(postDisplay.getBounds()); + + if (redraft) + { + String html = post.get("content").value; + StringBuilder b = new StringBuilder(); + Tree nodes; + nodes = RudimentaryHTMLParser.depthlessRead(html); + // We have to salvage whatever we can from the HTML. + for (Tree node: nodes) + { + if (node.key.equals("text")) + { + b.append(node.value); + } + if (node.key.equals("emoji")) + { + b.append(":" + node.value + ":"); + } + if (node.key.equals("tag")) + { + if (node.get(0).key.equals("/p")) + b.append("\n\n"); + if (node.get(0).key.equals("br")) + b.append("\n"); + if (node.get(0).key.equals("a")) { + b.append(node.get("href").value); + b.append(" "); + continue; + /* + * We don't omit the contents of the + * which is an automatic label, but we'll + * need a non-depthless parser to omit that. + * For now prioritise not losing anything + * from our composition. + */ + } + // I think that's all. I hope. + } + } + + String cw = post.get("spoiler_text").value; + + String vs = post.get("visibility").value; + PostVisibility v = null; + if (vs.equals("public")) v = PostVisibility.PUBLIC; + if (vs.equals("unlisted")) v = PostVisibility.UNLISTED; + if (vs.equals("private")) v = PostVisibility.FOLLOWERS; + if (vs.equals("direct")) v = PostVisibility.MENTIONED; + + String replyTo = post.get("in_reply_to_id").value; + + Composition c = new Composition(); + c.contentWarning = cw; + c.text = b.toString(); + c.visibility = v; + c.replyToPostId = replyTo; + + ComposeWindow w = primaire.getComposeWindow(); + w.setLocation(getX(), getY() + 100); + w.setVisible(true); + w.setComposition(c); + } + + api.deletePost(post.get("id").value, new RequestListener() { + + public void + connectionFailed(IOException eIo) + { + JOptionPane.showMessageDialog( + PostWindow.this, + "Failed to delete post.." + + "\n" + eIo.getMessage() + ); + } + + public void + requestFailed(int httpCode, Tree json) + { + JOptionPane.showMessageDialog( + PostWindow.this, + "Failed to delete post.." + + "\n" + json.get("error").value + + "\n(HTTP code: " + httpCode + ")" + ); + } + + public void + requestSucceeded(Tree json) + { + setVisible(false); + } + + }); + + postDisplay.setCursor(null); + postDisplay.setDeleteEnabled(true); + postDisplay.paintImmediately(postDisplay.getBounds()); + if (!isVisible()) dispose(); + } + // - -%- - public void @@ -379,6 +489,13 @@ implements ActionListener { profile, media; + private JPopupMenu + miscMenu; + + private JMenuItem + deletePost, + redraftPost; + // ---%-@-%--- public void @@ -475,6 +592,13 @@ implements ActionListener { favouriteBoost.setEnabled(a); } + public void + setDeleteEnabled(boolean a) + { + deletePost.setEnabled(a); + redraftPost.setEnabled(a); + } + public void setMediaPreview(Image n) { media.setImage(n); } @@ -510,7 +634,14 @@ implements ActionListener { if (src == replyMisc) { - if (command.startsWith("reply")) primaire.reply(); + if (command.startsWith("reply")) + primaire.reply(); + if (command.startsWith("misc")) + { + int rx = replyMisc.getWidth() / 2; + int ry = replyMisc.getHeight() - miscMenu.getHeight(); + miscMenu.show(replyMisc, rx, ry); + } return; } @@ -531,7 +662,20 @@ implements ActionListener { { primaire.openMedia(); return; - } + } + + if (src == deletePost) + { + primaire.deletePost(false); + return; + } + + if (src == redraftPost) + { + primaire.deletePost(true); + return; + } + } protected void @@ -584,6 +728,14 @@ implements ActionListener { nextPrev.addActionListener(this); media.addActionListener(this); + deletePost = new JMenuItem("Delete post"); + redraftPost = new JMenuItem("Delete and redraft post"); + deletePost.addActionListener(this); + redraftPost.addActionListener(this); + miscMenu = new JPopupMenu(); + miscMenu.add(deletePost); + miscMenu.add(redraftPost); + Box buttons = Box.createVerticalBox(); buttons.setOpaque(false); buttons.add(profile); diff --git a/RudimentaryHTMLParser.java b/RudimentaryHTMLParser.java index da1d9f9..665f4a6 100644 --- a/RudimentaryHTMLParser.java +++ b/RudimentaryHTMLParser.java @@ -26,26 +26,6 @@ RudimentaryHTMLParser { } } - public static String - stripTags(String html) - { - StringBuilder returnee = new StringBuilder(); - for (Tree node: depthlessRead(html)) - { - if (node.key.equals("tag")) continue; - - if (node.key.equals("emoji")) - { - returnee.append(":" + node.value + ":"); - } - if (node.key.equals("text")) - { - returnee.append(node.value); - } - } - return returnee.toString(); - } - // - -%- - private static Tree diff --git a/TimelineWindow.java b/TimelineWindow.java index 69c1ce8..4e61d43 100755 --- a/TimelineWindow.java +++ b/TimelineWindow.java @@ -450,7 +450,7 @@ implements ActionListener { } } -// - -%- - +// - -%- - private static String plainify(String html) @@ -620,12 +620,10 @@ implements String html = p.get("content").value; String cw = p.get("spoiler_text").value; - if (!cw.isEmpty()) { + if (!cw.isEmpty()) c.setBottom("(" + cw + ")"); - } - else { - c.setBottom(RudimentaryHTMLParser.stripTags(html) + " "); - } + else + c.setBottom(textApproximation(html) + " "); } for (int o = posts.size(); o < postPreviews.size(); ++o) { @@ -781,6 +779,34 @@ implements public void keyReleased(KeyEvent eK) { } +// - -%- - + + public static String + textApproximation(String html) + { + StringBuilder returnee = new StringBuilder(); + Tree nodes = RudimentaryHTMLParser.depthlessRead(html); + Tree first = nodes.get(0); + for (Tree node: nodes) + { + if (node.key.equals("tag")) + { + if (node.get(0).key.equals("br")) + returnee.append("; "); + if (node.get(0).key.equals("p") && node != first) + returnee.append("; "); + } + if (node.key.equals("emoji")) + { + returnee.append(":" + node.value + ":"); + } + if (node.key.equals("text")) + { + returnee.append(node.value); + } + } + return returnee.toString(); + } // ---%-@-%--- TimelineComponent(TimelineWindow primaire)