diff --git a/apps/syndie/java/src/net/i2p/syndie/sml/ThreadedHTMLRenderer.java b/apps/syndie/java/src/net/i2p/syndie/sml/ThreadedHTMLRenderer.java new file mode 100644 index 0000000000000000000000000000000000000000..68426eb2987f555ddcde5b6473652eb3148f3fce --- /dev/null +++ b/apps/syndie/java/src/net/i2p/syndie/sml/ThreadedHTMLRenderer.java @@ -0,0 +1,427 @@ +package net.i2p.syndie.sml; + +import java.io.*; +import java.text.*; +import java.util.*; +import net.i2p.I2PAppContext; +import net.i2p.client.naming.PetName; +import net.i2p.data.*; +import net.i2p.syndie.*; +import net.i2p.syndie.data.*; +import net.i2p.syndie.web.*; +import net.i2p.util.Log; + +/** + * + */ +public class ThreadedHTMLRenderer extends HTMLRenderer { + private Log _log; + private String _baseURI; + + public ThreadedHTMLRenderer(I2PAppContext ctx) { + super(ctx); + _log = ctx.logManager().getLog(ThreadedHTMLRenderer.class); + } + + /** what, if any, post should be rendered */ + public static final String PARAM_VIEW_POST = "post"; + /** what, if any, thread should be rendered in its entirety */ + public static final String PARAM_VIEW_THREAD = "thread"; + /** what post should be visible in the nav tree */ + public static final String PARAM_VISIBLE = "visible"; + public static final String PARAM_ADD_TO_GROUP_LOCATION = "addLocation"; + public static final String PARAM_ADD_TO_GROUP_NAME = "addGroup"; + /** index into the nav tree to start displaying */ + public static final String PARAM_OFFSET = "offset"; + public static final String PARAM_TAGS = "tags"; + + public static String getFilterByTagLink(String uri, ThreadNode node, User user, String tag) { + StringBuffer buf = new StringBuffer(64); + buf.append(uri).append('?'); + if (node != null) { + buf.append(PARAM_VIEW_POST).append('='); + buf.append(node.getEntry().getKeyHash().toBase64()).append('/'); + buf.append(node.getEntry().getEntryId()).append('&'); + } + + if ( (tag != null) && (tag.trim().length() > 0) ) + buf.append(PARAM_TAGS).append('=').append(tag); + return buf.toString(); + } + + public static String getNavLink(String uri, String viewPost, String viewThread, String tags, int offset) { + StringBuffer buf = new StringBuffer(64); + buf.append(uri); + buf.append('?'); + if (!empty(viewPost)) + buf.append(PARAM_VIEW_POST).append('=').append(viewPost).append('&'); + else if (!empty(viewThread)) + buf.append(PARAM_VIEW_THREAD).append('=').append(viewThread).append('&'); + + if (!empty(tags)) + buf.append(PARAM_TAGS).append('=').append(tags).append('&'); + + buf.append(PARAM_OFFSET).append('=').append(offset).append('&'); + + return buf.toString(); + } + + public static String getViewPostLink(String uri, ThreadNode node, User user, boolean isPermalink, + String offset, String tags) { + StringBuffer buf = new StringBuffer(64); + buf.append(uri); + if (node.getChildCount() > 0) { + buf.append('?').append(PARAM_VISIBLE).append('='); + ThreadNode child = node.getChild(0); + buf.append(child.getEntry().getKeyHash().toBase64()).append('/'); + buf.append(child.getEntry().getEntryId()).append('&'); + } else { + buf.append('?').append(PARAM_VISIBLE).append('='); + buf.append(node.getEntry().getKeyHash().toBase64()).append('/'); + buf.append(node.getEntry().getEntryId()).append('&'); + } + buf.append(PARAM_VIEW_POST).append('='); + buf.append(node.getEntry().getKeyHash().toBase64()).append('/'); + buf.append(node.getEntry().getEntryId()).append('&'); + + if ( (!isPermalink) && (!empty(offset)) ) + buf.append(PARAM_OFFSET).append('=').append(offset).append('&'); + + if ( (!isPermalink) && (!empty(tags)) ) + buf.append(PARAM_TAGS).append('=').append(tags).append('&'); + + return buf.toString(); + } + + + private static final boolean empty(String val) { return (val == null) || (val.trim().length() <= 0); } + + public void render(User user, Writer out, Archive archive, BlogURI post, + boolean inlineReply, ThreadIndex index, String baseURI, + String offset, String requestTags) throws IOException { + EntryContainer entry = archive.getEntry(post); + if (entry == null) return; + _entry = entry; + + _baseURI = baseURI; + _user = user; + _out = out; + _archive = archive; + _cutBody = false; + _showImages = true; + _headers = new HashMap(); + _bodyBuffer = new StringBuffer(1024); + _postBodyBuffer = new StringBuffer(1024); + _addresses = new ArrayList(); + _links = new ArrayList(); + _blogs = new ArrayList(); + _archives = new ArrayList(); + + _parser.parse(entry.getEntry().getText(), this); + + out.write("<!-- body begin -->\n"); + out.write("<!-- body meta begin -->\n"); + out.write("<tr class=\"postMeta\" id=\"" + post.toString() + "\">\n"); + + String subject = (String)_headers.get(HTMLRenderer.HEADER_SUBJECT); + if (subject == null) + subject = ""; + out.write(" <td colspan=\"3\" class=\"postMetaSubject\">"); + out.write(subject); + out.write("</td></tr>\n"); + out.write("<tr class=\"postMeta\"><td colspan=\"3\" class=\"postMetaLink\">\n"); + out.write("<a href=\""); + out.write(getMetadataURL(post.getKeyHash())); + out.write("\" title=\"View the author's profile\">"); + + String author = null; + PetName pn = user.getPetNameDB().getByLocation(post.getKeyHash().toBase64()); + if (pn == null) { + BlogInfo info = archive.getBlogInfo(post.getKeyHash()); + if (info != null) + author = info.getProperty(BlogInfo.NAME); + } else { + author = pn.getName(); + } + if ( (author == null) || (author.trim().length() <= 0) ) + author = post.getKeyHash().toBase64().substring(0,6); + + ThreadNode node = index.getNode(post); + + out.write(author); + out.write("</a> @ "); + out.write(getEntryDate(post.getEntryId())); + + Collection tags = node.getTags(); + if ( (tags != null) && (tags.size() > 0) ) { + out.write("\nTags: \n"); + for (Iterator tagIter = tags.iterator(); tagIter.hasNext(); ) { + String tag = (String)tagIter.next(); + out.write("<a href=\""); + out.write(getFilterByTagLink(baseURI, node, user, tag)); + out.write("\" title=\"Filter threads to only include posts tagged as '"); + out.write(tag); + out.write("'\">"); + out.write(" " + tag); + out.write("</a>\n"); + } + } + + out.write("\n<a href=\""); + out.write(getViewPostLink(baseURI, node, user, true, offset, requestTags)); + out.write("\" title=\"Select a shareable link directly to this post\">permalink</a>\n"); + + out.write("</td>\n</tr>\n"); + out.write("<!-- body meta end -->\n"); + out.write("<!-- body post begin -->\n"); + out.write("<tr class=\"postData\">\n"); + out.write("<td colspan=\"3\">\n"); + out.write(_bodyBuffer.toString()); + out.write("</td>\n</tr>\n"); + out.write("<!-- body post end -->\n"); + out.write("<!-- body details begin -->\n"); + out.write(_postBodyBuffer.toString()); +/* +"<tr class=\"postDetails\">\n" + +" <form action=\"viewattachment.jsp\" method=\"GET\">\n" + +" <td colspan=\"3\">\n" + +" External links:\n" + +" <a href=\"external.jsp?foo\" title=\"View foo.i2p\">http://foo.i2p/</a>\n" + +" <a href=\"external.jsp?bar\" title=\"View bar.i2p\">http://bar.i2p/</a>\n" + +" <br />\n" + +" Attachments: <select name=\"attachment\">\n" + +" <option value=\"0\">sampleRawSML.sml: Sample SML file with headers (4KB, type text/plain)</option>\n" + +" </select> <input type=\"submit\" name=\"action\" value=\"Download\" />\n" + +" <br /><a href=\"\" title=\"Expand the entire thread\">Full thread</a>\n" + +" <a href=\"\" title=\"Previous post in the thread\">Prev in thread</a> \n" + +" <a href=\"\" title=\"Next post in the thread\">Next in thread</a> \n" + +" </td>\n" + +" </form>\n" + +"</tr>\n" + + */ + out.write("<!-- body details end -->\n"); + if (inlineReply && user.getAuthenticated() ) { + String refuseReplies = (String)_headers.get(HTMLRenderer.HEADER_REFUSE_REPLIES); + // show the reply form if we are the author or replies have not been explicitly rejected + if ( (user.getBlog().equals(post.getKeyHash())) || + (refuseReplies == null) || (!Boolean.valueOf(refuseReplies).booleanValue()) ) { + out.write("<!-- body reply begin -->\n"); + out.write("<form action=\"post.jsp\" method=\"POST\" enctype=\"multipart/form-data\">\n"); + out.write("<input type=\"hidden\" name=\"inReplyTo\" value=\""); + out.write(Base64.encode(post.toString())); + out.write("\" />"); + out.write("<input type=\"hidden\" name=\"entrysubject\" value=\"re: "); + out.write(HTMLRenderer.sanitizeTagParam(subject)); + out.write("\" />"); + out.write("<tr class=\"postReply\">\n"); + out.write("<td colspan=\"3\">Reply: (<a href=\"smlref.jsp\" title=\"SML cheatsheet\">SML reference</a>)</td>\n</tr>\n"); + out.write("<tr class=\"postReplyText\">\n"); + out.write("<td colspan=\"3\"><textarea name=\"entrytext\" rows=\"2\" cols=\"100\"></textarea></td>\n"); + out.write("</tr>\n"); + out.write("<tr class=\"postReplyOptions\">\n"); + out.write(" <td colspan=\"3\">\n"); + out.write(" <input type=\"submit\" value=\"Preview...\" name=\"Post\" />\n"); + out.write(" Tags: <input type=\"text\" size=\"10\" name=\"entrytags\" />\n"); + out.write(" in a new thread? <input type=\"checkbox\" name=\"replyInNewThread\" />\n"); + out.write(" allow replies? <input type=\"checkbox\" name=\"allowReplies\" checked=\"true\" />\n"); + out.write(" attachment: <input type=\"file\" name=\"entryfile0\" />\n"); + out.write(" </td>\n</tr>\n</form>\n"); + out.write("<!-- body reply end -->\n"); + } + } + out.write("<!-- body end -->\n"); + } + + public void receiveEnd() { + _postBodyBuffer.append("<tr class=\"postDetails\">\n"); + _postBodyBuffer.append(" <form action=\"viewattachment.jsp\" method=\"GET\">\n"); + _postBodyBuffer.append(" <td colspan=\"3\" valign=\"top\" align=\"left\">\n"); + + _postBodyBuffer.append("<input type=\"hidden\" name=\"").append(ArchiveViewerBean.PARAM_BLOG); + _postBodyBuffer.append("\" value=\""); + if (_entry != null) + _postBodyBuffer.append(Base64.encode(_entry.getURI().getKeyHash().getData())); + else + _postBodyBuffer.append("unknown"); + _postBodyBuffer.append("\" />\n"); + _postBodyBuffer.append("<input type=\"hidden\" name=\"").append(ArchiveViewerBean.PARAM_ENTRY); + _postBodyBuffer.append("\" value=\""); + if (_entry != null) + _postBodyBuffer.append(_entry.getURI().getEntryId()); + else + _postBodyBuffer.append("unknown"); + _postBodyBuffer.append("\" />\n"); + + //_postBodyBuffer.append("<td colspan=\"2\" valign=\"top\" align=\"left\" ").append(getClass("summDetail")).append(" >\n"); + + if ( (_entry != null) && (_entry.getAttachments() != null) && (_entry.getAttachments().length > 0) ) { + _postBodyBuffer.append(getSpan("summDetailAttachment")).append("Attachments:</span> "); + _postBodyBuffer.append("<select ").append(getClass("summDetailAttachmentId")).append(" name=\"").append(ArchiveViewerBean.PARAM_ATTACHMENT).append("\">\n"); + for (int i = 0; i < _entry.getAttachments().length; i++) { + _postBodyBuffer.append("<option value=\"").append(i).append("\">"); + Attachment a = _entry.getAttachments()[i]; + _postBodyBuffer.append(sanitizeString(a.getName())); + if ( (a.getDescription() != null) && (a.getDescription().trim().length() > 0) ) { + _postBodyBuffer.append(": "); + _postBodyBuffer.append(sanitizeString(a.getDescription())); + } + _postBodyBuffer.append(" (").append(a.getDataLength()/1024).append("KB"); + _postBodyBuffer.append(", type ").append(sanitizeString(a.getMimeType())).append(")</option>\n"); + } + _postBodyBuffer.append("</select>\n"); + _postBodyBuffer.append("<input ").append(getClass("summDetailAttachmentDl")).append(" type=\"submit\" value=\"Download\" name=\"Download\" /><br />\n"); + } + + if (_blogs.size() > 0) { + _postBodyBuffer.append(getSpan("summDetailBlog")).append("Blog references:</span>"); + for (int i = 0; i < _blogs.size(); i++) { + Blog b = (Blog)_blogs.get(i); + _postBodyBuffer.append("<a ").append(getClass("summDetailBlogLink")).append(" href=\""); + boolean expanded = (_user != null ? _user.getShowExpanded() : false); + boolean images = (_user != null ? _user.getShowImages() : false); + _postBodyBuffer.append(getPageURL(new Hash(Base64.decode(b.hash)), b.tag, b.entryId, -1, -1, expanded, images)); + _postBodyBuffer.append("\">").append(sanitizeString(b.name)).append("</a> "); + } + _postBodyBuffer.append("<br />\n"); + } + + if (_links.size() > 0) { + _postBodyBuffer.append(getSpan("summDetailExternal")).append("External links:</span> "); + for (int i = 0; i < _links.size(); i++) { + Link l = (Link)_links.get(i); + _postBodyBuffer.append("<a ").append(getClass("summDetailExternalLink")).append(" href=\"externallink.jsp?"); + if (l.schema != null) + _postBodyBuffer.append("schema=").append(sanitizeURL(l.schema)).append('&'); + if (l.location != null) + _postBodyBuffer.append("location=").append(sanitizeURL(l.location)).append('&'); + _postBodyBuffer.append("\">").append(sanitizeString(l.location)); + _postBodyBuffer.append(getSpan("summDetailExternalNet")).append(" (").append(sanitizeString(l.schema)).append(")</span></a> "); + } + _postBodyBuffer.append("<br />\n"); + } + + if (_addresses.size() > 0) { + _postBodyBuffer.append(getSpan("summDetailAddr")).append("Addresses:</span>"); + for (int i = 0; i < _addresses.size(); i++) { + Address a = (Address)_addresses.get(i); + importAddress(a); + PetName pn = null; + if (_user != null) + pn = _user.getPetNameDB().getByLocation(a.location); + if (pn != null) { + _postBodyBuffer.append(' ').append(getSpan("summDetailAddrKnown")); + _postBodyBuffer.append(sanitizeString(pn.getName())).append("</span>"); + } else { + _postBodyBuffer.append(" <a ").append(getClass("summDetailAddrLink")).append(" href=\"addresses.jsp?"); + if (a.schema != null) + _postBodyBuffer.append("network=").append(sanitizeTagParam(a.schema)).append('&'); + if (a.location != null) + _postBodyBuffer.append("location=").append(sanitizeTagParam(a.location)).append('&'); + if (a.name != null) + _postBodyBuffer.append("name=").append(sanitizeTagParam(a.name)).append('&'); + if (a.protocol != null) + _postBodyBuffer.append("protocol=").append(sanitizeTagParam(a.protocol)).append('&'); + _postBodyBuffer.append("\">").append(sanitizeString(a.name)).append("</a>"); + } + } + _postBodyBuffer.append("<br />\n"); + } + + if (_archives.size() > 0) { + _postBodyBuffer.append(getSpan("summDetailArchive")).append("Archives:</span>"); + for (int i = 0; i < _archives.size(); i++) { + ArchiveRef a = (ArchiveRef)_archives.get(i); + _postBodyBuffer.append(" <a ").append(getClass("summDetailArchiveLink")).append(" href=\"").append(getArchiveURL(null, new SafeURL(a.locationSchema + "://" + a.location))); + _postBodyBuffer.append("\">").append(sanitizeString(a.name)).append("</a>"); + if (a.description != null) + _postBodyBuffer.append(": ").append(getSpan("summDetailArchiveDesc")).append(sanitizeString(a.description)).append("</span>"); + if (null == _user.getPetNameDB().getByLocation(a.location)) { + _postBodyBuffer.append(" <a ").append(getClass("summDetailArchiveBookmark")).append(" href=\""); + _postBodyBuffer.append(getBookmarkURL(a.name, a.location, a.locationSchema, "syndiearchive")); + _postBodyBuffer.append("\">bookmark it</a>"); + } + } + _postBodyBuffer.append("<br />\n"); + } + + if (_entry != null) { + List replies = _archive.getIndex().getReplies(_entry.getURI()); + if ( (replies != null) && (replies.size() > 0) ) { + _postBodyBuffer.append(getSpan("summDetailReplies")).append("Replies:</span> "); + for (int i = 0; i < replies.size(); i++) { + BlogURI reply = (BlogURI)replies.get(i); + _postBodyBuffer.append("<a ").append(getClass("summDetailReplyLink")).append(" href=\""); + _postBodyBuffer.append(getPageURL(reply.getKeyHash(), null, reply.getEntryId(), -1, -1, true, _user.getShowImages())); + _postBodyBuffer.append("\">"); + _postBodyBuffer.append(getSpan("summDetailReplyAuthor")); + BlogInfo replyAuthor = _archive.getBlogInfo(reply); + if (replyAuthor != null) { + _postBodyBuffer.append(sanitizeString(replyAuthor.getProperty(BlogInfo.NAME))); + } else { + _postBodyBuffer.append(reply.getKeyHash().toBase64().substring(0,16)); + } + _postBodyBuffer.append("</span> on "); + _postBodyBuffer.append(getSpan("summDetailReplyDate")); + _postBodyBuffer.append(getEntryDate(reply.getEntryId())); + _postBodyBuffer.append("</a></span> "); + } + _postBodyBuffer.append("<br />"); + } + } + + String inReplyTo = (String)_headers.get(HEADER_IN_REPLY_TO); + if ( (inReplyTo != null) && (inReplyTo.trim().length() > 0) ) { + BlogURI replyURI = new BlogURI(inReplyTo); + if (replyURI.getEntryId() > 0) + _postBodyBuffer.append(" <a ").append(getClass("summDetailParent")).append(" href=\"").append(getPageURL(replyURI.getKeyHash(), null, replyURI.getEntryId(), 0, 0, true, true)).append("\">(view parent)</a><br />\n"); + } + + _postBodyBuffer.append(" </td>\n"); + _postBodyBuffer.append(" </form>\n"); + _postBodyBuffer.append("</tr>\n"); + } + + public void receiveHeaderEnd() { + //_preBodyBuffer.append("<table ").append(getClass("overall")).append(" width=\"100%\" border=\"0\">\n"); + //renderSubjectCell(); + //renderMetaCell(); + //renderPreBodyCell(); + } + + protected String getEntryURL() { return getEntryURL(_user != null ? _user.getShowImages() : false); } + protected String getEntryURL(boolean showImages) { + if (_entry == null) + return _baseURI; + else + return _baseURI + '?' + PARAM_VIEW_POST + '=' + + Base64.encode(_entry.getURI().getKeyHash().getData()) + '/' + + _entry.getURI().getEntryId() + '&'; + } + + public String getPageURL(User user, String selector, int numPerPage, int pageNum) { return _baseURI; } + + public String getPageURL(Hash blog, String tag, long entryId, String group, int numPerPage, int pageNum, boolean expandEntries, boolean showImages) { + StringBuffer buf = new StringBuffer(128); + buf.append(_baseURI).append('?'); + if ( (blog != null) && (entryId > 0) ) { + buf.append(PARAM_VIEW_POST).append('=').append(Base64.encode(blog.getData())).append('/').append(entryId).append('&'); + buf.append(PARAM_VISIBLE).append('=').append(Base64.encode(blog.getData())).append('/').append(entryId).append('&'); + } + if (tag != null) + buf.append(PARAM_TAGS).append('=').append(sanitizeTagParam(tag)).append('&'); + return buf.toString(); + } + public String getArchiveURL(Hash blog, SafeURL archiveLocation) { + return "remote.jsp?" + //+ "action=Continue..." // should this be the case? + + "&schema=" + sanitizeTagParam(archiveLocation.getSchema()) + + "&location=" + sanitizeTagParam(archiveLocation.getLocation()); + } + public String getBookmarkURL(String name, String location, String schema, String protocol) { + return "addresses.jsp?name=" + sanitizeTagParam(name) + + "&network=" + sanitizeTagParam(schema) + + "&protocol=" + sanitizeTagParam(protocol) + + "&location=" + sanitizeTagParam(location); + + } +}