Refactored back to objects. Moved entity unpacking to object classes.
Added composition length counter. Fixed timeline background image bug.
0
ClipboardApi.java
Executable file → Normal file
56
ComposeWindow.java
Executable file → Normal file
@ -15,8 +15,12 @@ import java.awt.BorderLayout;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.KeyListener;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.Cursor;
|
||||
import java.awt.Color;
|
||||
import javax.swing.event.CaretListener;
|
||||
import javax.swing.event.CaretEvent;
|
||||
|
||||
import cafe.biskuteri.hinoki.Tree;
|
||||
import java.io.IOException;
|
||||
@ -92,7 +96,7 @@ ComposeWindow extends JFrame {
|
||||
ComposeWindow.this,
|
||||
"Tried to submit post, failed..."
|
||||
+ "\n" + json.get("error").value
|
||||
+ "(HTTP error code: " + httpCode + ")"
|
||||
+ "\n(HTTP error code: " + httpCode + ")"
|
||||
);
|
||||
}
|
||||
|
||||
@ -194,7 +198,7 @@ ComposeWindow extends JFrame {
|
||||
|
||||
class
|
||||
ComposeComponent extends JPanel
|
||||
implements ActionListener {
|
||||
implements ActionListener, CaretListener, KeyListener {
|
||||
|
||||
private ComposeWindow
|
||||
primaire;
|
||||
@ -207,6 +211,9 @@ implements ActionListener {
|
||||
private JTextField
|
||||
reply, contentWarning;
|
||||
|
||||
private JLabel
|
||||
textLength;
|
||||
|
||||
private JComboBox<String>
|
||||
visibility;
|
||||
|
||||
@ -294,6 +301,42 @@ implements ActionListener {
|
||||
public void
|
||||
actionPerformed(ActionEvent eA) { primaire.submit(); }
|
||||
|
||||
public void
|
||||
caretUpdate(CaretEvent eCa) { updateTextLength(); }
|
||||
|
||||
public void
|
||||
keyPressed(KeyEvent eK) { updateTextLength(); }
|
||||
|
||||
public void
|
||||
keyReleased(KeyEvent eK) { }
|
||||
|
||||
public void
|
||||
keyTyped(KeyEvent eK) { }
|
||||
|
||||
private void
|
||||
updateTextLength()
|
||||
{
|
||||
int length = text.getText().length();
|
||||
/*
|
||||
* The web interface doesn't do this expensive thing.
|
||||
* It has an upwards counter, incremented by I'm not
|
||||
* sure what. Presumably they have some control over
|
||||
* the text input. I'd rather not, cause I use a
|
||||
* Japanese IME, I'm going to see how laggy this is.
|
||||
* It raises our app's system requirements, but, I was
|
||||
* going to transition it to multithreading anyways,
|
||||
* I don't think we're going to be very cheap.. Which
|
||||
* sucks, but the Mastodon API is not helping us here.
|
||||
*/
|
||||
textLength.setText(Integer.toString(length));
|
||||
/*
|
||||
* Another thing I could do is temporarily move the
|
||||
* caret to the end and then find its position, then
|
||||
* seek back. Not sure how much that would help, but
|
||||
* if this is too laggy, that's what I'd try next.
|
||||
*/
|
||||
}
|
||||
|
||||
// ---%-@-%---
|
||||
|
||||
ComposeComponent(ComposeWindow primaire)
|
||||
@ -321,6 +364,9 @@ implements ActionListener {
|
||||
top.add(cwLabel);
|
||||
top.add(contentWarning);
|
||||
|
||||
textLength = new JLabel("0");
|
||||
textLength.setFont(textLength.getFont().deriveFont(14f));
|
||||
|
||||
visibility = new JComboBox<>(new String[] {
|
||||
"Public",
|
||||
"Unlisted",
|
||||
@ -335,8 +381,10 @@ implements ActionListener {
|
||||
|
||||
Box bottom = Box.createHorizontalBox();
|
||||
bottom.add(Box.createGlue());
|
||||
bottom.add(textLength);
|
||||
bottom.add(Box.createHorizontalStrut(12));
|
||||
bottom.add(visibility);
|
||||
bottom.add(Box.createHorizontalStrut(8));
|
||||
bottom.add(Box.createHorizontalStrut(12));
|
||||
bottom.add(submit);
|
||||
|
||||
text = new JTextArea();
|
||||
@ -344,6 +392,8 @@ implements ActionListener {
|
||||
text.setWrapStyleWord(true);
|
||||
text.setFont(text.getFont().deriveFont(16f));
|
||||
text.setBorder(bc);
|
||||
text.addCaretListener(this);
|
||||
text.addKeyListener(this);
|
||||
|
||||
setLayout(new BorderLayout(0, 8));
|
||||
add(top, BorderLayout.NORTH);
|
||||
|
24
ImageApi.java
Executable file → Normal file
@ -11,8 +11,8 @@ ImageApi {
|
||||
public static Image
|
||||
local(String name)
|
||||
{
|
||||
String path = "/graphics/" + name + ".png";
|
||||
URL url = ImageApi.class.getResource(name);
|
||||
String path = "graphics/" + name + ".png";
|
||||
URL url = ImageApi.class.getResource(path);
|
||||
if (url == null) return null;
|
||||
return new ImageIcon(url).getImage();
|
||||
}
|
||||
@ -20,12 +20,28 @@ ImageApi {
|
||||
public static Image
|
||||
remote(String urlr)
|
||||
{
|
||||
try {
|
||||
try
|
||||
{
|
||||
URL url = new URL(urlr);
|
||||
Toolkit TK = Toolkit.getDefaultToolkit();
|
||||
return TK.createImage(url);
|
||||
}
|
||||
catch (MalformedURLException eMu) {
|
||||
catch (MalformedURLException eMu)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static ImageIcon
|
||||
iconRemote(String urlr)
|
||||
{
|
||||
if (urlr == null) return null;
|
||||
try
|
||||
{
|
||||
return new ImageIcon(new URL(urlr));
|
||||
}
|
||||
catch (MalformedURLException eMu)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
0
ImageWindow.java
Executable file → Normal file
438
JKomasto.java
Executable file → Normal file
@ -2,12 +2,21 @@
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.ImageIcon;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Cursor;
|
||||
import java.awt.Image;
|
||||
import java.awt.FontMetrics;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.text.BreakIterator;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.Period;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import cafe.biskuteri.hinoki.Tree;
|
||||
|
||||
|
||||
@ -126,7 +135,8 @@ TimelineType {
|
||||
FEDERATED,
|
||||
LOCAL,
|
||||
HOME,
|
||||
LIST
|
||||
LIST,
|
||||
PROFILE
|
||||
|
||||
}
|
||||
|
||||
@ -154,9 +164,22 @@ TimelinePage {
|
||||
public String
|
||||
accountNumId, listId;
|
||||
|
||||
public Tree<String>
|
||||
public Post[]
|
||||
posts;
|
||||
|
||||
// ---%-@-%---
|
||||
|
||||
public
|
||||
TimelinePage() { }
|
||||
|
||||
public
|
||||
TimelinePage(Tree<String> entity)
|
||||
{
|
||||
posts = new Post[entity.size()];
|
||||
for (int o = 0; o < posts.length; ++o)
|
||||
posts[o] = new Post(entity.get(o));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -175,9 +198,313 @@ Notification {
|
||||
}
|
||||
|
||||
|
||||
|
||||
class
|
||||
Post {
|
||||
|
||||
public String
|
||||
id,
|
||||
uri;
|
||||
|
||||
public Account
|
||||
author;
|
||||
|
||||
public PostVisibility
|
||||
visibility;
|
||||
|
||||
public String
|
||||
text,
|
||||
approximateText;
|
||||
|
||||
public List<RichTextPane.Segment>
|
||||
formattedText,
|
||||
laidoutText;
|
||||
|
||||
public String
|
||||
contentWarning;
|
||||
|
||||
public ZonedDateTime
|
||||
dateTime;
|
||||
|
||||
public String
|
||||
date, time, relativeTime;
|
||||
|
||||
public boolean
|
||||
boosted,
|
||||
favourited;
|
||||
|
||||
public Post
|
||||
boostedPost;
|
||||
|
||||
public Attachment[]
|
||||
attachments;
|
||||
|
||||
public String[][]
|
||||
emojiUrls;
|
||||
|
||||
// - -%- -
|
||||
|
||||
private static final DateTimeFormatter
|
||||
DATE_FORMAT = DateTimeFormatter.ofPattern("d LLLL ''uu"),
|
||||
TIME_FORMAT = DateTimeFormatter.ofPattern("HH:mm");
|
||||
|
||||
// ---%-@-%---
|
||||
|
||||
public void
|
||||
resolveApproximateText()
|
||||
{
|
||||
assert text != null;
|
||||
if (approximateText != null) return;
|
||||
|
||||
Tree<String> nodes;
|
||||
nodes = RudimentaryHTMLParser.depthlessRead(text);
|
||||
if (nodes.size() == 0)
|
||||
{
|
||||
approximateText = "-";
|
||||
return;
|
||||
}
|
||||
|
||||
StringBuilder b = new StringBuilder();
|
||||
Tree<String> first = nodes.get(0);
|
||||
for (Tree<String> node: nodes)
|
||||
{
|
||||
if (node.key.equals("tag"))
|
||||
{
|
||||
if (node.get(0).key.equals("br"))
|
||||
b.append("; ");
|
||||
if (node.get(0).key.equals("p") && node != first)
|
||||
b.append("; ");
|
||||
}
|
||||
if (node.key.equals("emoji"))
|
||||
{
|
||||
b.append(":" + node.value + ":");
|
||||
}
|
||||
if (node.key.equals("text"))
|
||||
{
|
||||
b.append(node.value);
|
||||
}
|
||||
}
|
||||
approximateText = b.toString();
|
||||
}
|
||||
|
||||
public void
|
||||
resolveFormattedText()
|
||||
{
|
||||
assert text != null;
|
||||
assert emojiUrls != null;
|
||||
if (formattedText != null) return;
|
||||
|
||||
RichTextPane.Builder b = new RichTextPane.Builder();
|
||||
Tree<String> nodes;
|
||||
nodes = RudimentaryHTMLParser.depthlessRead(text);
|
||||
for (Tree<String> node: nodes)
|
||||
{
|
||||
if (node.key.equals("tag"))
|
||||
{
|
||||
String tagName = node.get(0).key;
|
||||
Tree<String> href = node.get("href");
|
||||
|
||||
if (tagName.equals("br"))
|
||||
b = b.spacer("\n");
|
||||
if (tagName.equals("/p"))
|
||||
b = b.spacer("\n").spacer("\n");
|
||||
if (tagName.equals("a"))
|
||||
b = b.link(href.value, null).spacer(" ");
|
||||
}
|
||||
if (node.key.equals("text"))
|
||||
{
|
||||
BreakIterator it;
|
||||
it = BreakIterator.getWordInstance(Locale.ROOT);
|
||||
it.setText(node.value);
|
||||
int start = it.first(), end = it.next();
|
||||
while (end != BreakIterator.DONE)
|
||||
{
|
||||
String word = text.substring(start, end);
|
||||
char c = word.isEmpty() ? ' ' : word.charAt(0);
|
||||
boolean w = Character.isWhitespace(c);
|
||||
b = w ? b.spacer(word) : b.text(word);
|
||||
start = end;
|
||||
end = it.next();
|
||||
}
|
||||
}
|
||||
if (node.key.equals("emoji"))
|
||||
{
|
||||
String shortcode = node.value;
|
||||
|
||||
String url = null;
|
||||
for (String[] mapping: emojiUrls)
|
||||
{
|
||||
String ms = mapping[0];
|
||||
String mu = mapping[1];
|
||||
if (ms.equals(shortcode)) url = mu;
|
||||
}
|
||||
|
||||
ImageIcon icon = ImageApi.iconRemote(url);
|
||||
if (icon != null) b = b.image(icon, node.value);
|
||||
else b = b.text(":" + node.value + ":");
|
||||
}
|
||||
}
|
||||
formattedText = b.finish();
|
||||
}
|
||||
|
||||
public int
|
||||
resolveLaidoutText(int width, FontMetrics fm)
|
||||
{
|
||||
assert formattedText != null;
|
||||
|
||||
laidoutText = RichTextPane.layout(formattedText, fm, width);
|
||||
|
||||
int maxY = 0;
|
||||
for (RichTextPane.Segment segment: laidoutText)
|
||||
if (segment.y > maxY) maxY = segment.y;
|
||||
|
||||
return maxY;
|
||||
}
|
||||
|
||||
public void
|
||||
resolveRelativeTime()
|
||||
{
|
||||
assert date != null;
|
||||
|
||||
ZonedDateTime now = ZonedDateTime.now();
|
||||
long d = ChronoUnit.SECONDS.between(dateTime, now);
|
||||
long s = Math.abs(d);
|
||||
if (s < 30) relativeTime = "now";
|
||||
else if (s < 60) relativeTime = d + "s";
|
||||
else if (s < 3600) relativeTime = (d / 60) + "m";
|
||||
else if (s < 86400) relativeTime = (d / 3600) + "h";
|
||||
else relativeTime = (d / 86400) + "d";
|
||||
}
|
||||
|
||||
// ---%-@-%---
|
||||
|
||||
public
|
||||
Post() { }
|
||||
|
||||
public
|
||||
Post(Tree<String> entity)
|
||||
{
|
||||
id = entity.get("id").value;
|
||||
|
||||
uri = entity.get("url").value;
|
||||
if (uri == null) uri = entity.get("uri").value;
|
||||
|
||||
author = new Account(entity.get("account"));
|
||||
|
||||
String v = entity.get("visibility").value;
|
||||
boolean p = v.equals("public");
|
||||
boolean u = v.equals("unlisted");
|
||||
boolean f = v.equals("private");
|
||||
boolean m = v.equals("direct");
|
||||
if (p) visibility = PostVisibility.PUBLIC;
|
||||
if (u) visibility = PostVisibility.UNLISTED;
|
||||
if (f) visibility = PostVisibility.FOLLOWERS;
|
||||
if (m) visibility = PostVisibility.MENTIONED;
|
||||
|
||||
dateTime =
|
||||
ZonedDateTime.parse(entity.get("created_at").value)
|
||||
.withZoneSameInstant(ZoneId.systemDefault());
|
||||
date = DATE_FORMAT.format(dateTime);
|
||||
time = TIME_FORMAT.format(dateTime);
|
||||
|
||||
text = entity.get("content").value;
|
||||
String st = entity.get("spoiler_text").value;
|
||||
contentWarning = st.trim().isEmpty() ? null : st;
|
||||
|
||||
String favourited = entity.get("favourited").value;
|
||||
String boosted = entity.get("reblogged").value;
|
||||
this.favourited = favourited.equals("true");
|
||||
this.boosted = boosted.equals("true");
|
||||
|
||||
Tree<String> media = entity.get("media_attachments");
|
||||
attachments = new Attachment[media.size()];
|
||||
for (int o = 0; o < attachments.length; ++o)
|
||||
{
|
||||
attachments[o] = new Attachment(media.get(o));
|
||||
}
|
||||
|
||||
Tree<String> emojis = entity.get("emojis");
|
||||
emojiUrls = new String[emojis.size()][];
|
||||
for (int o = 0; o < emojiUrls.length; ++o)
|
||||
{
|
||||
Tree<String> emoji = emojis.get(o);
|
||||
String[] mapping = emojiUrls[o] = new String[2];
|
||||
mapping[0] = emoji.get("shortcode").value;
|
||||
mapping[1] = emoji.get("url").value;
|
||||
}
|
||||
|
||||
Tree<String> boostedPost = entity.get("reblog");
|
||||
if (boostedPost.size() > 0)
|
||||
this.boostedPost = new Post(boostedPost);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
class
|
||||
Account {
|
||||
|
||||
public String
|
||||
numId;
|
||||
|
||||
public String
|
||||
id,
|
||||
name;
|
||||
|
||||
public List<RichTextPane.Segment>
|
||||
formattedName;
|
||||
|
||||
public String
|
||||
avatarUrl;
|
||||
|
||||
public Image
|
||||
avatar;
|
||||
|
||||
// ---%-@-%---
|
||||
|
||||
public void
|
||||
resolveFormattedName()
|
||||
{
|
||||
assert name != null;
|
||||
formattedName =
|
||||
new RichTextPane.Builder().text(name).finish();
|
||||
}
|
||||
|
||||
public void
|
||||
resolveAvatar()
|
||||
{
|
||||
assert avatarUrl != null;
|
||||
if (avatar != null) return;
|
||||
avatar = ImageApi.remote(avatarUrl);
|
||||
}
|
||||
|
||||
// ---%-@-%---
|
||||
|
||||
public
|
||||
Account() { }
|
||||
|
||||
public
|
||||
Account(Tree<String> entity)
|
||||
{
|
||||
numId = entity.get("id").value;
|
||||
id = entity.get("acct").value;
|
||||
|
||||
String displayName = entity.get("display_name").value;
|
||||
String username = entity.get("username").value;
|
||||
name = displayName.isEmpty() ? username : displayName;
|
||||
|
||||
avatarUrl = entity.get("avatar").value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
class
|
||||
Attachment {
|
||||
|
||||
public String
|
||||
id;
|
||||
|
||||
public String
|
||||
type;
|
||||
|
||||
@ -190,6 +517,34 @@ Attachment {
|
||||
public Image
|
||||
image;
|
||||
|
||||
// ---%-@-%---
|
||||
|
||||
public void
|
||||
resolveImage()
|
||||
{
|
||||
assert url != null;
|
||||
if (image != null) return;
|
||||
if (!type.equals("image")) return;
|
||||
image = ImageApi.remote(url);
|
||||
}
|
||||
|
||||
// ---%-@-%---
|
||||
|
||||
public
|
||||
Attachment() { }
|
||||
|
||||
public
|
||||
Attachment(Tree<String> entity)
|
||||
{
|
||||
String u1 = entity.get("remote_url").value;
|
||||
String u2 = entity.get("text_url").value;
|
||||
String u3 = entity.get("url").value;
|
||||
|
||||
url = u1 != null ? u1 : u2 != null ? u2 : u3;
|
||||
type = entity.get("type").value;
|
||||
description = entity.get("description").value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -207,4 +562,83 @@ Composition {
|
||||
public String
|
||||
replyToPostId;
|
||||
|
||||
// ---%-@-%---
|
||||
|
||||
public
|
||||
Composition() { }
|
||||
|
||||
public static Composition
|
||||
reply(Tree<String> entity, String ownNumId)
|
||||
{
|
||||
Composition c = new Composition();
|
||||
|
||||
Tree<String> boosted = entity.get("reblog");
|
||||
if (boosted.size() > 0) entity = boosted;
|
||||
|
||||
String st = entity.get("spoiler_text").value;
|
||||
String ri = entity.get("id").value;
|
||||
c.contentWarning = st.trim().isEmpty() ? null : st;
|
||||
c.replyToPostId = ri.trim().isEmpty() ? null : ri;
|
||||
|
||||
Tree<String> author = entity.get("account");
|
||||
String authorId = author.get("acct").value;
|
||||
String authorNumId = author.get("id").value;
|
||||
c.text = "";
|
||||
if (!authorNumId.equals(ownNumId))
|
||||
c.text = "@" + authorId + " ";
|
||||
|
||||
String visibility = entity.get("visibility").value;
|
||||
boolean p = visibility.equals("public");
|
||||
boolean u = visibility.equals("unlisted");
|
||||
boolean f = visibility.equals("private");
|
||||
boolean m = visibility.equals("direct");
|
||||
assert p || u || f || m;
|
||||
if (p) c.visibility = PostVisibility.PUBLIC;
|
||||
if (u) c.visibility = PostVisibility.UNLISTED;
|
||||
if (f) c.visibility = PostVisibility.FOLLOWERS;
|
||||
if (m) c.visibility = PostVisibility.MENTIONED;
|
||||
// Less eye strain arranged this way.
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
public static Composition
|
||||
recover(Tree<String> entity)
|
||||
{
|
||||
assert entity.get("text") != null;
|
||||
|
||||
Composition c = new Composition();
|
||||
c.text = entity.get("text").value;
|
||||
c.contentWarning = entity.get("spoiler_text").value;
|
||||
c.replyToPostId = entity.get("in_reply_to_id").value;
|
||||
|
||||
String visibility = entity.get("visibility").value;
|
||||
boolean p = visibility.equals("public");
|
||||
boolean u = visibility.equals("unlisted");
|
||||
boolean f = visibility.equals("private");
|
||||
boolean m = visibility.equals("direct");
|
||||
assert p || u || f || m;
|
||||
if (p) c.visibility = PostVisibility.PUBLIC;
|
||||
if (u) c.visibility = PostVisibility.UNLISTED;
|
||||
if (f) c.visibility = PostVisibility.FOLLOWERS;
|
||||
if (m) c.visibility = PostVisibility.MENTIONED;
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
public static Composition
|
||||
reply(Post post, String ownNumId)
|
||||
{
|
||||
if (post.boostedPost != null) post = post.boostedPost;
|
||||
|
||||
Composition c = new Composition();
|
||||
c.replyToPostId = post.id;
|
||||
c.visibility = post.visibility;
|
||||
c.contentWarning = post.contentWarning;
|
||||
c.text = "";
|
||||
if (!post.author.numId.equals(ownNumId))
|
||||
c.text = "@" + post.author.id + " ";
|
||||
return c;
|
||||
}
|
||||
|
||||
}
|
||||
|
0
KDE_Dialog_Appear.wav
Executable file → Normal file
0
LoginWindow.java
Executable file → Normal file
0
MastodonApi.java
Executable file → Normal file
11
NotificationsWindow.java
Executable file → Normal file
@ -87,13 +87,10 @@ NotificationsWindow extends JFrame {
|
||||
|
||||
if (n.type != NotificationType.FOLLOW)
|
||||
{
|
||||
Tree<String> post = t.get("status");
|
||||
String pid, phtml, ptext;
|
||||
pid = post.get("id").value;
|
||||
phtml = post.get("content").value;
|
||||
ptext = TimelineWindow.textApproximation(phtml);
|
||||
n.postId = pid;
|
||||
n.postText = ptext;
|
||||
Post post = new Post(t.get("status"));
|
||||
post.resolveApproximateText();
|
||||
n.postId = post.id;
|
||||
n.postText = post.approximateText;
|
||||
}
|
||||
|
||||
notifications.add(n);
|
||||
|
274
PostWindow.java
Executable file → Normal file
@ -49,8 +49,9 @@ PostWindow extends JFrame {
|
||||
private MastodonApi
|
||||
api;
|
||||
|
||||
private Tree<String>
|
||||
post;
|
||||
private Post
|
||||
post,
|
||||
wrapperPost;
|
||||
|
||||
// - -%- -
|
||||
|
||||
@ -65,78 +66,67 @@ PostWindow extends JFrame {
|
||||
|
||||
// ---%-@-%---
|
||||
|
||||
public void
|
||||
readEntity(Tree<String> post)
|
||||
{
|
||||
this.post = post;
|
||||
public void
|
||||
use(Post post)
|
||||
{
|
||||
assert post != null;
|
||||
|
||||
Tree<String> boosted = post.get("reblog");
|
||||
if (boosted.size() > 0) post = boosted;
|
||||
if (post.boostedPost != null)
|
||||
{
|
||||
wrapperPost = post;
|
||||
this.post = post.boostedPost;
|
||||
post = post.boostedPost;
|
||||
}
|
||||
else
|
||||
{
|
||||
wrapperPost = null;
|
||||
this.post = post;
|
||||
}
|
||||
|
||||
Tree<String> author = post.get("account");
|
||||
Tree<String> emojis = post.get("emojis");
|
||||
Tree<String> media = post.get("media_attachments");
|
||||
display.setAuthorName(post.author.name);
|
||||
display.setAuthorId(post.author.id);
|
||||
|
||||
String an = author.get("display_name").value;
|
||||
if (an.isEmpty()) an = author.get("username").value;
|
||||
display.setAuthorName(an);
|
||||
display.setAuthorId(author.get("acct").value);
|
||||
|
||||
String aid = author.get("id").value;
|
||||
String oid = api.getAccountDetails().get("id").value;
|
||||
String aid = post.author.numId;
|
||||
display.setDeleteEnabled(aid.equals(oid));
|
||||
|
||||
String avurl = author.get("avatar").value;
|
||||
display.setAuthorAvatar(ImageApi.remote(avurl));
|
||||
post.author.resolveAvatar();
|
||||
display.setAuthorAvatar(post.author.avatar);
|
||||
|
||||
String sdate = post.get("created_at").value;
|
||||
ZonedDateTime date = ZonedDateTime.parse(sdate);
|
||||
date = date.withZoneSameInstant(ZoneId.systemDefault());
|
||||
display.setDate(DATE_FORMAT.format(date));
|
||||
display.setTime(TIME_FORMAT.format(date));
|
||||
display.setDate(post.date);
|
||||
display.setTime(post.time);
|
||||
|
||||
String[][] emojiUrls = new String[emojis.size()][];
|
||||
for (int o = 0; o < emojiUrls.length; ++o) {
|
||||
Tree<String> emoji = emojis.get(o);
|
||||
emojiUrls[o] = new String[2];
|
||||
emojiUrls[o][0] = emoji.get("shortcode").value;
|
||||
emojiUrls[o][1] = emoji.get("url").value;
|
||||
}
|
||||
display.setEmojiUrls(emojiUrls);
|
||||
display.setEmojiUrls(post.emojiUrls);
|
||||
|
||||
display.setHtml(post.get("content").value);
|
||||
boolean f = post.get("favourited").value.equals("true");
|
||||
boolean b = post.get("reblogged").value.equals("true");
|
||||
display.setFavourited(f);
|
||||
display.setBoosted(b);
|
||||
display.setHtml(post.text);
|
||||
display.setFavourited(post.favourited);
|
||||
display.setBoosted(post.boosted);
|
||||
|
||||
if (media.size() > 0)
|
||||
{
|
||||
Tree<String> first = media.get(0);
|
||||
String u1 = first.get("remote_url").value;
|
||||
String u2 = first.get("text_url").value;
|
||||
String u3 = first.get("url").value;
|
||||
String purl = u1 != null ? u1 : u2 != null ? u2 : u3;
|
||||
display.setMediaPreview(ImageApi.remote(purl));
|
||||
}
|
||||
else display.setMediaPreview(null);
|
||||
if (post.attachments.length > 0)
|
||||
{
|
||||
post.attachments[0].resolveImage();
|
||||
display.setMediaPreview(post.attachments[0].image);
|
||||
}
|
||||
else display.setMediaPreview(null);
|
||||
|
||||
String html = post.get("content").value;
|
||||
setTitle(TimelineWindow.textApproximation(html));
|
||||
post.resolveApproximateText();
|
||||
this.setTitle(post.approximateText);
|
||||
|
||||
display.resetFocus();
|
||||
repaint();
|
||||
}
|
||||
|
||||
public void
|
||||
readEntity(Tree<String> post)
|
||||
{
|
||||
use(new Post(post));
|
||||
}
|
||||
|
||||
public void
|
||||
openAuthorProfile()
|
||||
{
|
||||
Tree<String> post = this.post;
|
||||
Tree<String> boosted = post.get("reblog");
|
||||
if (boosted.size() > 0) post = boosted;
|
||||
|
||||
TimelineWindow w = new TimelineWindow(primaire);
|
||||
w.showAuthorPosts(post.get("account").get("id").value);
|
||||
w.showAuthorPosts(post.author.numId);
|
||||
w.showLatestPage();
|
||||
w.setLocationRelativeTo(this);
|
||||
w.setVisible(true);
|
||||
@ -145,11 +135,7 @@ PostWindow extends JFrame {
|
||||
public void
|
||||
favourite(boolean favourited)
|
||||
{
|
||||
Tree<String> post = this.post;
|
||||
Tree<String> boosted = post.get("reblog");
|
||||
if (boosted.size() > 0) post = boosted;
|
||||
|
||||
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
||||
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
||||
display.setFavouriteBoostEnabled(false);
|
||||
display.paintImmediately(display.getBounds());
|
||||
RequestListener handler = new RequestListener() {
|
||||
@ -178,13 +164,11 @@ PostWindow extends JFrame {
|
||||
public void
|
||||
requestSucceeded(Tree<String> json)
|
||||
{
|
||||
String n = Boolean.toString(favourited);
|
||||
PostWindow.this.post.get("favourited").value = n;
|
||||
PostWindow.this.post.favourited = favourited;
|
||||
}
|
||||
|
||||
};
|
||||
String postId = post.get("id").value;
|
||||
api.setPostFavourited(postId, favourited, handler);
|
||||
api.setPostFavourited(post.id, favourited, handler);
|
||||
display.setCursor(null);
|
||||
display.setFavouriteBoostEnabled(true);
|
||||
display.repaint();
|
||||
@ -193,10 +177,6 @@ PostWindow extends JFrame {
|
||||
public void
|
||||
boost(boolean boosted)
|
||||
{
|
||||
Tree<String> post = this.post;
|
||||
Tree<String> boosted2 = post.get("reblog");
|
||||
if (boosted2.size() > 0) post = boosted2;
|
||||
|
||||
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
||||
display.setFavouriteBoostEnabled(false);
|
||||
display.paintImmediately(display.getBounds());
|
||||
@ -226,13 +206,11 @@ PostWindow extends JFrame {
|
||||
public void
|
||||
requestSucceeded(Tree<String> json)
|
||||
{
|
||||
String n = Boolean.toString(boosted);
|
||||
PostWindow.this.post.get("reblogged").value = n;
|
||||
PostWindow.this.post.boosted = boosted;
|
||||
}
|
||||
|
||||
};
|
||||
String postId = post.get("id").value;
|
||||
api.setPostBoosted(postId, boosted, handler);
|
||||
api.setPostBoosted(post.id, boosted, handler);
|
||||
display.setCursor(null);
|
||||
display.setFavouriteBoostEnabled(true);
|
||||
display.repaint();
|
||||
@ -241,66 +219,28 @@ PostWindow extends JFrame {
|
||||
public void
|
||||
reply()
|
||||
{
|
||||
Tree<String> post = this.post;
|
||||
Tree<String> boosted = post.get("reblog");
|
||||
if (boosted.size() > 0) post = boosted;
|
||||
|
||||
String authorId = post.get("account").get("acct").value;
|
||||
String postId = post.get("id").value;
|
||||
String cw = post.get("spoiler_text").value;
|
||||
|
||||
String id1 = post.get("account").get("id").value;
|
||||
String id2 = api.getAccountDetails().get("id").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;
|
||||
|
||||
Composition c = new Composition();
|
||||
c.contentWarning = cw;
|
||||
c.text = id1.equals(id2) ? "" : "@" + authorId + " ";
|
||||
c.visibility = v;
|
||||
c.replyToPostId = postId;
|
||||
|
||||
String ownId = api.getAccountDetails().get("id").value;
|
||||
Composition c = Composition.reply(this.post, ownId);
|
||||
ComposeWindow w = primaire.getComposeWindow();
|
||||
w.setComposition(c);
|
||||
w.setLocation(getX(), getY() + 100);
|
||||
w.setVisible(true);
|
||||
w.setComposition(c);
|
||||
}
|
||||
|
||||
public void
|
||||
openMedia()
|
||||
{
|
||||
Tree<String> post = this.post;
|
||||
Tree<String> boosted = post.get("reblog");
|
||||
if (boosted.size() > 0) post = boosted;
|
||||
Tree<String> media = post.get("media_attachments");
|
||||
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
||||
|
||||
Attachment[] as = new Attachment[media.size()];
|
||||
for (int o = 0; o < as.length; ++o)
|
||||
{
|
||||
Tree<String> medium = media.get(o);
|
||||
String u1 = medium.get("remote_url").value;
|
||||
String u2 = medium.get("text_url").value;
|
||||
String u3 = medium.get("url").value;
|
||||
for (Attachment a: post.attachments) a.resolveImage();
|
||||
|
||||
Attachment a = as[o] = new Attachment();
|
||||
a.url = u1 != null ? u1 : u2 != null ? u2 : u3;
|
||||
a.type = medium.get("type").value;
|
||||
a.description = medium.get("description").value;
|
||||
a.image = ImageApi.remote(a.url);
|
||||
}
|
||||
ImageWindow w = new ImageWindow();
|
||||
w.setTitle("Media - " + this.getTitle());
|
||||
w.showAttachments(post.attachments);
|
||||
w.setLocationRelativeTo(null);
|
||||
w.setVisible(true);
|
||||
|
||||
ImageWindow w = primaire.getMediaWindow();
|
||||
w.setTitle(post.get("id").value);
|
||||
w.showAttachments(as);
|
||||
if (!w.isVisible()) {
|
||||
w.setLocationRelativeTo(null);
|
||||
w.setVisible(true);
|
||||
}
|
||||
display.setCursor(null);
|
||||
}
|
||||
|
||||
public void
|
||||
@ -310,69 +250,7 @@ PostWindow extends JFrame {
|
||||
display.setDeleteEnabled(false);
|
||||
display.paintImmediately(display.getBounds());
|
||||
|
||||
if (redraft)
|
||||
{
|
||||
String html = post.get("content").value;
|
||||
StringBuilder b = new StringBuilder();
|
||||
Tree<String> nodes;
|
||||
nodes = RudimentaryHTMLParser.depthlessRead(html);
|
||||
// We have to salvage whatever we can from the HTML.
|
||||
for (Tree<String> 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 <a>
|
||||
* 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() {
|
||||
api.deletePost(post.id, new RequestListener() {
|
||||
|
||||
public void
|
||||
connectionFailed(IOException eIo)
|
||||
@ -399,6 +277,15 @@ PostWindow extends JFrame {
|
||||
requestSucceeded(Tree<String> json)
|
||||
{
|
||||
setVisible(false);
|
||||
|
||||
if (redraft)
|
||||
{
|
||||
Composition c = Composition.recover(json);
|
||||
ComposeWindow w = new ComposeWindow(primaire);
|
||||
w.setComposition(c);
|
||||
w.setLocation(getX(), getY() + 100);
|
||||
w.setVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@ -412,35 +299,20 @@ PostWindow extends JFrame {
|
||||
public void
|
||||
copyPostId()
|
||||
{
|
||||
Tree<String> post = this.post;
|
||||
Tree<String> reblogged = post.get("reblog");
|
||||
if (reblogged.size() > 0) post = reblogged;
|
||||
|
||||
ClipboardApi.serve(post.get("id").value);
|
||||
ClipboardApi.serve(post.id);
|
||||
}
|
||||
|
||||
public void
|
||||
copyPostLink()
|
||||
{
|
||||
Tree<String> post = this.post;
|
||||
Tree<String> reblogged = post.get("reblog");
|
||||
if (reblogged.size() > 0) post = reblogged;
|
||||
|
||||
String url = post.get("url").value;
|
||||
if (url == null) url = post.get("uri").value;
|
||||
|
||||
ClipboardApi.serve(url);
|
||||
ClipboardApi.serve(post.uri);
|
||||
}
|
||||
|
||||
public void
|
||||
openReplies()
|
||||
{
|
||||
Tree<String> post = this.post;
|
||||
Tree<String> boosted = post.get("reblog");
|
||||
if (boosted.size() > 0) post = boosted;
|
||||
|
||||
RepliesWindow w = new RepliesWindow(primaire, this);
|
||||
w.showFor(post.get("id").value);
|
||||
w.showFor(post.id);
|
||||
w.setLocation(getX(), getY() + 100);
|
||||
w.setVisible(true);
|
||||
}
|
||||
|
5
RepliesWindow.java
Executable file → Normal file
@ -267,8 +267,9 @@ implements TreeSelectionListener {
|
||||
public String
|
||||
toString()
|
||||
{
|
||||
String html = post.get("content").value;
|
||||
return TimelineWindow.textApproximation(html);
|
||||
Post post = new Post(this.post);
|
||||
post.resolveApproximateText();
|
||||
return post.approximateText;
|
||||
}
|
||||
|
||||
// -=%=-
|
||||
|
0
RequestListener.java
Executable file → Normal file
0
RichTextPane.java
Executable file → Normal file
0
RudimentaryHTMLParser.java
Executable file → Normal file
209
TimelineWindow.java
Executable file → Normal file
@ -91,64 +91,42 @@ implements ActionListener {
|
||||
|
||||
// ---%-@-%---
|
||||
|
||||
public void
|
||||
readEntity(Tree<String> postEntityArray)
|
||||
{
|
||||
page.posts = postEntityArray;
|
||||
public void
|
||||
use(TimelinePage page)
|
||||
{
|
||||
assert page != null;
|
||||
this.page = page;
|
||||
|
||||
List<PostPreviewComponent> previews;
|
||||
List<PostPreviewComponent> previews;
|
||||
previews = display.getPostPreviews();
|
||||
|
||||
int available = postEntityArray.size();
|
||||
int available = page.posts.length;
|
||||
int max = previews.size();
|
||||
assert available <= max;
|
||||
|
||||
for (int o = 0; o < available; ++o)
|
||||
{
|
||||
PostPreviewComponent c = previews.get(o);
|
||||
Tree<String> p = postEntityArray.get(o);
|
||||
Tree<String> a = p.get("account");
|
||||
PostPreviewComponent preview = previews.get(o);
|
||||
Post post = page.posts[o];
|
||||
|
||||
String an = a.get("display_name").value;
|
||||
if (an.isEmpty()) an = a.get("username").value;
|
||||
c.setTopLeft(an);
|
||||
|
||||
Tree<String> boosted = p.get("reblog");
|
||||
if (boosted.size() > 0) {
|
||||
c.setTopLeft("boosted by " + an);
|
||||
p = boosted;
|
||||
a = p.get("account");
|
||||
preview.setTopLeft(post.author.name);
|
||||
if (post.boostedPost != null)
|
||||
{
|
||||
String s = "boosted by " + post.author.name;
|
||||
preview.setTopLeft(s);
|
||||
post = post.boostedPost;
|
||||
}
|
||||
|
||||
String f = "";
|
||||
if (p.get("media_attachments").size() > 0) f += "a";
|
||||
String t = "";
|
||||
try {
|
||||
String jv = p.get("created_at").value;
|
||||
ZonedDateTime pv = ZonedDateTime.parse(jv);
|
||||
ZoneId z = ZoneId.systemDefault();
|
||||
pv = pv.withZoneSameInstant(z);
|
||||
String flags = "";
|
||||
if (post.attachments.length > 0) flags += "a";
|
||||
post.resolveRelativeTime();
|
||||
preview.setTopRight(flags + " " + post.relativeTime);
|
||||
|
||||
ZonedDateTime now = ZonedDateTime.now();
|
||||
long d = ChronoUnit.SECONDS.between(pv, now);
|
||||
long s = Math.abs(d);
|
||||
if (s < 30) t = "now";
|
||||
else if (s < 60) t = d + "s";
|
||||
else if (s < 3600) t = (d / 60) + "m";
|
||||
else if (s < 86400) t = (d / 3600) + "h";
|
||||
else t = (d / 86400) + "d";
|
||||
}
|
||||
catch (DateTimeParseException eDt) {
|
||||
assert false;
|
||||
}
|
||||
c.setTopRight(f + " " + t);
|
||||
|
||||
String html = p.get("content").value;
|
||||
String cw = p.get("spoiler_text").value;
|
||||
if (!cw.isEmpty())
|
||||
c.setBottom("(" + cw + ")");
|
||||
else
|
||||
c.setBottom(textApproximation(html));
|
||||
post.resolveApproximateText();
|
||||
if (post.contentWarning != null)
|
||||
preview.setBottom("(" + post.contentWarning + ")");
|
||||
else
|
||||
preview.setBottom(post.approximateText);
|
||||
}
|
||||
for (int o = available; o < max; ++o)
|
||||
{
|
||||
@ -159,6 +137,16 @@ implements ActionListener {
|
||||
display.setNextPageAvailable(full);
|
||||
display.setPreviousPageAvailable(true);
|
||||
display.resetFocus();
|
||||
}
|
||||
|
||||
public void
|
||||
readEntity(Tree<String> postEntityArray)
|
||||
{
|
||||
TimelinePage page = new TimelinePage(postEntityArray);
|
||||
page.type = this.page.type;
|
||||
page.accountNumId = this.page.accountNumId;
|
||||
page.listId = this.page.listId;
|
||||
use(page);
|
||||
}
|
||||
|
||||
public void
|
||||
@ -168,9 +156,8 @@ implements ActionListener {
|
||||
if (!showingLatest)
|
||||
{
|
||||
assert page.posts != null;
|
||||
assert page.posts.size() != 0;
|
||||
Tree<String> first = page.posts.get(0);
|
||||
firstId = first.get("id").value;
|
||||
assert page.posts.length != 0;
|
||||
firstId = page.posts[0].id;
|
||||
}
|
||||
|
||||
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
||||
@ -204,7 +191,8 @@ implements ActionListener {
|
||||
public void
|
||||
requestSucceeded(Tree<String> json)
|
||||
{
|
||||
if (json.size() < PREVIEW_COUNT) {
|
||||
if (json.size() < PREVIEW_COUNT)
|
||||
{
|
||||
showLatestPage();
|
||||
return;
|
||||
}
|
||||
@ -264,9 +252,8 @@ implements ActionListener {
|
||||
showNextPage()
|
||||
{
|
||||
assert page.posts != null;
|
||||
assert page.posts.size() != 0;
|
||||
Tree<String> last = page.posts.get(page.posts.size() - 1);
|
||||
String lastId = last.get("id").value;
|
||||
assert page.posts.length != 0;
|
||||
String lastId = page.posts[page.posts.length - 1].id;
|
||||
|
||||
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
||||
api.getTimelinePage(
|
||||
@ -299,7 +286,8 @@ implements ActionListener {
|
||||
public void
|
||||
requestSucceeded(Tree<String> json)
|
||||
{
|
||||
if (json.size() == 0) {
|
||||
if (json.size() == 0)
|
||||
{
|
||||
// We should probably say something
|
||||
// to the user here? For now, we
|
||||
// quietly cancel.
|
||||
@ -319,9 +307,8 @@ implements ActionListener {
|
||||
showPreviousPage()
|
||||
{
|
||||
assert page.posts != null;
|
||||
assert page.posts.size() != 0;
|
||||
Tree<String> first = page.posts.get(0);
|
||||
String firstId = first.get("id").value;
|
||||
assert page.posts.length != 0;
|
||||
String firstId = page.posts[0].id;
|
||||
|
||||
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
||||
api.getTimelinePage(
|
||||
@ -354,7 +341,8 @@ implements ActionListener {
|
||||
public void
|
||||
requestSucceeded(Tree<String> json)
|
||||
{
|
||||
if (json.size() < PREVIEW_COUNT) {
|
||||
if (json.size() < PREVIEW_COUNT)
|
||||
{
|
||||
showLatestPage();
|
||||
return;
|
||||
}
|
||||
@ -368,19 +356,19 @@ implements ActionListener {
|
||||
display.setCursor(null);
|
||||
}
|
||||
|
||||
|
||||
public void
|
||||
setTimelineType(TimelineType type)
|
||||
{
|
||||
assert type != TimelineType.LIST;
|
||||
assert type != TimelineType.PROFILE;
|
||||
|
||||
page.type = type;
|
||||
page.accountNumId = null;
|
||||
setTitle(toString(type) + " timeline - JKomasto");
|
||||
|
||||
String s1 = type.toString();
|
||||
s1 = s1.charAt(0) + s1.substring(1).toLowerCase();
|
||||
setTitle(s1 + " - JKomasto");
|
||||
|
||||
String s2 = type.toString().toLowerCase();
|
||||
display.setBackgroundImage(ImageApi.local(s2));
|
||||
String f = type.toString().toLowerCase();
|
||||
display.setBackgroundImage(ImageApi.local(f));
|
||||
display.repaint();
|
||||
}
|
||||
|
||||
public TimelineType
|
||||
@ -391,19 +379,20 @@ implements ActionListener {
|
||||
{
|
||||
assert authorNumId != null;
|
||||
|
||||
page.type = TimelineType.FEDERATED;
|
||||
page.type = TimelineType.PROFILE;
|
||||
page.accountNumId = authorNumId;
|
||||
|
||||
setTitle(authorNumId + " - JKomasto");
|
||||
|
||||
display.setBackgroundImage(ImageApi.local("profile"));
|
||||
display.repaint();
|
||||
}
|
||||
|
||||
public void
|
||||
openOwnProfile()
|
||||
{
|
||||
Tree<String> accountDetails = api.getAccountDetails();
|
||||
assert accountDetails != null;
|
||||
String id = accountDetails.get("id").value;
|
||||
assert id != null;
|
||||
|
||||
TimelineWindow w = new TimelineWindow(primaire);
|
||||
w.showAuthorPosts(id);
|
||||
@ -463,7 +452,8 @@ implements ActionListener {
|
||||
api.getAccounts(query, handler);
|
||||
if (handler.json == null) return;
|
||||
|
||||
if (handler.json.size() == 0) {
|
||||
if (handler.json.size() == 0)
|
||||
{
|
||||
JOptionPane.showMessageDialog(
|
||||
this,
|
||||
"There were no results from the query.. ☹️"
|
||||
@ -508,6 +498,12 @@ implements ActionListener {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (id == null) return;
|
||||
/*
|
||||
* It seems like this can happen if someone
|
||||
* presses escape out of the confirm dialog.
|
||||
* I don't know why that doesn't map to cancel.
|
||||
*/
|
||||
}
|
||||
|
||||
TimelineWindow w = new TimelineWindow(primaire);
|
||||
@ -522,18 +518,19 @@ implements ActionListener {
|
||||
public void
|
||||
previewSelected(int index)
|
||||
{
|
||||
if (index > page.posts.size()) return;
|
||||
Tree<String> post = page.posts.get(index - 1);
|
||||
primaire.getAutoViewWindow().readEntity(post);
|
||||
if (index > page.posts.length) return;
|
||||
Post post = page.posts[index - 1];
|
||||
primaire.getAutoViewWindow().use(post);
|
||||
}
|
||||
|
||||
public void
|
||||
previewOpened(int index)
|
||||
{
|
||||
if (index > page.posts.size()) return;
|
||||
Tree<String> post = page.posts.get(index - 1);
|
||||
if (index > page.posts.length) return;
|
||||
Post post = page.posts[index - 1];
|
||||
|
||||
PostWindow w = new PostWindow(primaire);
|
||||
w.readEntity(post);
|
||||
w.use(post);
|
||||
w.setLocationRelativeTo(this);
|
||||
w.setVisible(true);
|
||||
}
|
||||
@ -578,8 +575,10 @@ implements ActionListener {
|
||||
}
|
||||
if (src == openNotifications)
|
||||
{
|
||||
NotificationsWindow w = primaire.getNotificationsWindow();
|
||||
if (!w.isVisible()) {
|
||||
NotificationsWindow w =
|
||||
primaire.getNotificationsWindow();
|
||||
if (!w.isVisible())
|
||||
{
|
||||
w.setLocationByPlatform(true);
|
||||
w.setVisible(true);
|
||||
}
|
||||
@ -601,54 +600,16 @@ implements ActionListener {
|
||||
}
|
||||
}
|
||||
|
||||
// - -%- -
|
||||
|
||||
public static String
|
||||
textApproximation(String html)
|
||||
{
|
||||
StringBuilder returnee = new StringBuilder();
|
||||
Tree<String> nodes = RudimentaryHTMLParser.depthlessRead(html);
|
||||
if (nodes.size() == 0) return "-";
|
||||
Tree<String> first = nodes.get(0);
|
||||
for (Tree<String> 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();
|
||||
}
|
||||
|
||||
// - -%- -
|
||||
|
||||
private static String
|
||||
plainify(String html)
|
||||
toString(TimelineType type)
|
||||
{
|
||||
// Delete all tags.
|
||||
StringBuilder b = new StringBuilder();
|
||||
boolean in = false;
|
||||
for (char c: html.toCharArray()) switch (c) {
|
||||
case '<': in = true; break;
|
||||
case '>': in = false; break;
|
||||
default: if (!in) b.append(c);
|
||||
switch (type)
|
||||
{
|
||||
case FEDERATED: return "Federated";
|
||||
case LOCAL: return "Local";
|
||||
case HOME: return "Home";
|
||||
default: return "";
|
||||
}
|
||||
String s = b.toString();
|
||||
s = s.replaceAll("<", "<");
|
||||
s = s.replaceAll(">", ">");
|
||||
s = s.replaceAll(" ", "");
|
||||
return s;
|
||||
}
|
||||
|
||||
// ---%-@-%---
|
||||
@ -704,7 +665,7 @@ implements ActionListener {
|
||||
setJMenuBar(menuBar);
|
||||
|
||||
page = new TimelinePage();
|
||||
page.posts = new Tree<String>();
|
||||
page.posts = new Post[0];
|
||||
|
||||
display = new TimelineComponent(this);
|
||||
display.setNextPageAvailable(false);
|
||||
@ -925,6 +886,8 @@ implements
|
||||
pageLabel = new JLabel("0");
|
||||
|
||||
Box bottom = Box.createHorizontalBox();
|
||||
bottom.setBackground(null);
|
||||
bottom.setOpaque(false);
|
||||
bottom.add(Box.createGlue());
|
||||
bottom.add(prev);
|
||||
bottom.add(Box.createHorizontalStrut(8));
|
||||
|
0
TwoToggleButton.java
Executable file → Normal file
0
WindowUpdater.java
Executable file → Normal file
0
graphics/Federated.xcf
Executable file → Normal file
0
graphics/Flags.xcf
Executable file → Normal file
0
graphics/Hourglass.xcf
Executable file → Normal file
0
graphics/boostToggled.png
Executable file → Normal file
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
0
graphics/boostUntoggled.png
Executable file → Normal file
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
0
graphics/button.png
Executable file → Normal file
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
0
graphics/disabledOverlay.png
Executable file → Normal file
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
0
graphics/favouriteToggled.png
Executable file → Normal file
Before Width: | Height: | Size: 353 B After Width: | Height: | Size: 353 B |
0
graphics/favouriteUntoggled.png
Executable file → Normal file
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
0
graphics/federated.png
Executable file → Normal file
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
0
graphics/miscToggled.png
Executable file → Normal file
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
0
graphics/miscUntoggled.png
Executable file → Normal file
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
0
graphics/ref1.png
Executable file → Normal file
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
0
graphics/replyToggled.png
Executable file → Normal file
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
0
graphics/replyUntoggled.png
Executable file → Normal file
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
0
graphics/selectedOverlay.png
Executable file → Normal file
Before Width: | Height: | Size: 313 B After Width: | Height: | Size: 313 B |
0
graphics/test1.png
Executable file → Normal file
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
0
graphics/test2.png
Executable file → Normal file
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
0
graphics/test3.png
Executable file → Normal file
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
0
graphics/test4.png
Executable file → Normal file
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |