aboutsummaryrefslogtreecommitdiff
path: root/views
diff options
context:
space:
mode:
authorFChannel <>2021-10-24 10:40:45 -0700
committerFChannel <>2022-06-19 12:53:29 -0700
commit48fefb76c0a908cc3fa00abc9c090ce3ac8cb560 (patch)
treea7a24aec9f43222949e1f004fd5cd8a247e5024e /views
parent65e79e6a743f447f320595dafec530212a568b9d (diff)
gofiber conversion, index, board posts, board post hooked up
Diffstat (limited to 'views')
-rw-r--r--views/css/themes/default.css249
-rw-r--r--views/css/themes/gruvbox.css235
-rw-r--r--views/index.html53
-rw-r--r--views/js/footerscript.js44
-rw-r--r--views/js/posts.js344
-rw-r--r--views/js/themes.js40
-rw-r--r--views/js/timer.js38
-rw-r--r--views/layouts/main.html50
-rw-r--r--views/npost.html66
-rw-r--r--views/nposts.html70
-rw-r--r--views/partials/bottom.html48
-rw-r--r--views/partials/footer.html13
-rw-r--r--views/partials/posts.html122
-rw-r--r--views/partials/postscripts.html3
-rw-r--r--views/partials/top.html128
15 files changed, 1503 insertions, 0 deletions
diff --git a/views/css/themes/default.css b/views/css/themes/default.css
new file mode 100644
index 0000000..98edc29
--- /dev/null
+++ b/views/css/themes/default.css
@@ -0,0 +1,249 @@
+a, a:link, a:visited, a:hover, a:active {
+ text-decoration: none
+}
+
+a:link, a:visited, a:active {
+ color: black;
+}
+
+a:hover {
+ color: #de0808;
+}
+
+body {
+ background-color: #eef2fe;
+ color: black;
+}
+
+body.nsfw {
+ background-color: #ffffee;
+ color: #820404
+}
+
+h1, h2, h3, h4, h5, h6 {
+ color: #af0a0f;
+}
+
+.popup-box {
+ border: 4px solid #d3caf0;
+ background-color: #eff5ff;
+}
+
+.nsfw .popup-box {
+ border: 4px solid #f0e2d9;
+ background-color: #f9f9e0;
+}
+
+.box {
+ background-color: #eff5ff;
+}
+
+.nsfw .box {
+ background-color: #f9f9e0;
+}
+
+.box-alt {
+ background-color: #d3caf0;
+}
+
+.nsfw .box-alt {
+ background-color: #f0e2d9;
+}
+
+
+.quote {
+ color: #789922;
+}
+
+.post {
+ background-color: #d5daf0;
+}
+
+.nsfw .post {
+ background-color: #f0e0d6;
+}
+
+:target > div > .post {
+ background-color: #d6bad0;
+}
+
+.nsfw :target > div > .post {
+ background-color: #f0c0b0;
+}
+
+.title {
+ color: #0f0c5d;
+}
+
+.name, .tripcode {
+ color: #117743;
+}
+
+a.reply {
+ color: #af0a0f;
+ text-decoration: 1px underline;
+}
+
+.replyLink {
+ color: #000080;
+ font-size: 0.8em;
+}
+
+#newpostbtn {
+ text-align: center;
+ margin-top: 80px;
+}
+
+#postForm {
+ margin: auto;
+}
+
+#postForm tr > td:first-child {
+ background-color: #98e;
+ border: 1px black;
+ padding-left: 0.5em;
+ padding-right: 0.5em;
+}
+
+.nsfw #postForm tr > td:first-child {
+ background-color: #ea8;
+}
+
+#postForm input[type="text"],
+#postForm textarea,
+#reply-name, #reply-options, #reply-comment {
+ box-sizing: border-box;
+ -webkit-box-sizing:border-box;
+ -moz-box-sizing: border-box;
+}
+
+#postForm input[type="text"],
+#postForm textarea,
+#reply-name, #reply-options, #reply-comment {
+ box-sizing: border-box;
+ -webkit-box-sizing:border-box;
+ -moz-box-sizing: border-box;
+}
+
+#reply-comment {
+ min-width: 300px;
+ width: 396px;
+ height: 200px;
+}
+
+#reply-name {
+ width: 75%;
+ float: left;
+}
+
+#reply-options {
+ width: 25%;
+ float: right;
+}
+
+#reply-header {
+ display: inline-block;
+ width: 100%;
+ cursor: move;
+}
+
+#postForm #captcha {
+ display: block;
+ width: 100%;
+}
+
+.popup-box {
+ position: fixed;
+ min-width: 300px;
+ width: min-content;
+ z-index: 9;
+ display: block;
+}
+
+/* TODO: rename */
+.box2 {
+ border: 4px solid #f0e2d9;
+ background-color: #f9f9e0;
+}
+
+.newsbox {
+ padding: 25px;
+ border: 4px solid #f0e2d9;
+ background-color: #f9f9e0;
+}
+
+.newsbox h2 {
+ margin: 0;
+ padding: 0;
+}
+
+.newsbox-news {
+ text-align: left;
+ margin-top: 25px;
+ padding: 25px;
+}
+
+.newsbox-news p,
+.newsbox-news h3 {
+ margin: 0;
+}
+
+#stopTablePost {
+ float: right;
+ display: none;
+}
+
+#boardGrid {
+ display: grid;
+ grid-auto-columns: 1fr;
+ border: 4px solid #820404;
+ background-color: #f9f9e0;
+}
+
+#boardGridHeader {
+ border-bottom: 2px solid #820404;
+ display: inline-grid;
+}
+
+.boardGridCell {
+ white-space: nowrap;
+ display: inline-grid;
+ text-align: left;
+ padding: 5px;
+ border-top: 2px solid #820404;
+ border-left: 2px solid #820404;
+}
+
+/* these may or may not work. my CSS is poor so i just kinda did stuff until it worked. */
+.boardGridCell:nth-child(-n+4) {
+ border-top: none;
+}
+
+.boardGridCell:nth-child(3n+2) {
+ border-left: none;
+}
+
+#threadfooter {
+ width: 100%;
+ table-layout: fixed;
+ border-collapse: collapse;
+}
+
+#threadfooter td {
+ padding: 0;
+ margin: 0;
+}
+
+#threadfooter #threadStats {
+ float: right;
+}
+
+#navlinks, #boardlinks {
+ padding: 0;
+ margin: 0;
+}
+
+#navlinks > li,
+#boardlinks > li {
+ display: inline;
+}
diff --git a/views/css/themes/gruvbox.css b/views/css/themes/gruvbox.css
new file mode 100644
index 0000000..8704482
--- /dev/null
+++ b/views/css/themes/gruvbox.css
@@ -0,0 +1,235 @@
+a, a:link, a:visited, a:active {
+ color: #b16286;
+ text-decoration: none
+}
+
+a.reply {
+ color: #cc241d;
+ text-decoration: 1px underline;
+}
+
+a:hover.reply {
+ color: #fb4934;
+}
+
+body {
+ background: #282828;
+ color: #ebdbb2;
+
+ font-family: monospace, sans-serif;
+ font-size: 0.9em;
+}
+
+.popup-box {
+ border: 4px solid #928374;
+ background-color: #3c3836;
+}
+
+.box, .box-alt {
+ background-color: #3c3836;
+}
+
+.quote {
+ color: #98971a;
+}
+
+.post {
+ background-color: #1d2021;
+}
+
+:target > div > .post {
+ background-color: #504945;
+}
+
+.subject {
+ color: #458588;
+}
+
+.name {
+ color: #b8bb26;
+}
+
+.tripcode {
+ color: #689d6a;
+}
+
+h1,h2,h3,h4,h5,h6 {
+ color: #fb4934;
+ margin-bottom: 0.1em;
+}
+
+.replyLink {
+ color: #83a598;
+ font-size: 0.8em;
+}
+
+#newpostbtn {
+ text-align: center;
+ margin-top: 80px;
+}
+
+input[type="text"] {
+ -webkit-appearance: none;
+ -webkit-border-radius: 0;
+}
+
+#postForm {
+ border: 4px solid #928374;
+ background-color: #3c3836;
+ margin: auto;
+}
+
+#postForm tr > td:first-child {
+ background-color: #504945;
+ padding-left: 0.5em;
+ padding-right: 0.5em;
+}
+
+#postForm input[type="text"],
+#postForm textarea,
+#reply-name, #reply-options, #reply-comment {
+ background-color: #504945;
+ color: #ebdbb2;
+ border: 0;
+ border-bottom: 2px solid #3c3836;
+ font-family: monospace, sans-serif;
+
+ box-sizing: border-box;
+ -webkit-box-sizing:border-box;
+ -moz-box-sizing: border-box;
+}
+
+#postForm input[type="text"]:focus,
+#postForm textarea:focus,
+#reply-name:focus, #reply-options:focus, #reply-comment:focus {
+ outline: none;
+}
+
+#reply-comment {
+ min-width: 300px;
+ width: 396px;
+ height: 200px;
+}
+
+#reply-name {
+ width: 75%;
+ float: left;
+}
+
+#reply-options {
+ width: 25%;
+ border-left: 2px solid #3c3836;
+ float: right;
+}
+
+#reply-header {
+ display: inline-block;
+ width: 100%;
+ cursor: move;
+}
+
+#postForm #captcha {
+ display: block;
+ width: 100%;
+}
+
+.popup-box {
+ position: fixed;
+ min-width: 300px;
+ width: min-content;
+ z-index: 9;
+ display: block;
+}
+
+/* TODO: rename */
+.box2 {
+ border: 4px solid #928374;
+ background-color: #3c3836;
+}
+
+.newsbox {
+ padding: 25px;
+ border: 4px solid #928374;
+ background-color: #3c3836;
+}
+
+.newsbox h2 {
+ margin: 0;
+ padding: 0;
+}
+
+.newsbox-news {
+ text-align: left;
+ background-color: #504945;
+ margin-top: 25px;
+ padding: 25px;
+}
+
+.newsbox-news p,
+.newsbox-news h3 {
+ margin: 0;
+}
+
+#stopTablePost {
+ float: right;
+ display: none;
+}
+
+#boardGrid {
+ display: grid;
+ grid-auto-columns: 1fr;
+ border: 4px solid #928374;
+ background-color: #3c3836;
+}
+
+#boardGridHeader {
+ border-bottom: 2px solid #928374;
+ display: inline-grid;
+}
+
+.boardGridCell {
+ white-space: nowrap;
+ display: inline-grid;
+ text-align: left;
+ padding: 5px;
+ border-top: 2px solid #928374;
+ border-left: 2px solid #928374;
+}
+
+/* these may or may not work. my CSS is poor so i just kinda did stuff until it worked. */
+.boardGridCell:nth-child(-n+4) {
+ border-top: none;
+}
+
+.boardGridCell:nth-child(3n+2) {
+ border-left: none;
+}
+
+#threadfooter {
+ width: 100%;
+ table-layout: fixed;
+ border-collapse: collapse;
+}
+
+#threadfooter td {
+ padding: 0;
+ margin: 0;
+}
+
+#threadfooter #threadStats {
+ float: right;
+}
+
+#navlinks, #boardlinks {
+ padding: 0;
+ margin: 0;
+}
+
+#navlinks > li,
+#boardlinks > li {
+ display: inline;
+}
+
+hr {
+ border: 1px solid #928374;
+}
diff --git a/views/index.html b/views/index.html
new file mode 100644
index 0000000..f426d5d
--- /dev/null
+++ b/views/index.html
@@ -0,0 +1,53 @@
+<div style="text-align: center; max-width: 800px; margin: 0 auto;">
+ <h1>{{ .page.Title }}</h1>
+ <p style="text-align: justify">{{ .page.PreferredUsername }} is a federated image board based on <a href="https://activitypub.rocks/">ActivityPub</a>. The current version of the code running on the server is still a work-in-progress product, expect a bumpy ride for the time being. Get the server code here: <a href="https://github.com/FChannel0">https://github.com/FChannel0</a>.</p>
+
+ {{ if .page.Boards }}
+ {{ $l := len .page.Boards }}
+ <div style="margin-top:50px;">
+ <div id="boardGrid">
+ {{ if lt $l 2 }}
+ <div id="boardGridHeader"><span style="font-size: 1.5em;font-weight: bold;">Local boards</span></div>
+ {{ else if eq $l 2 }}
+ <div id="boardGridHeader" style="grid-column: 1 / 3;"><span style="font-size: 1.5em;font-weight: bold;">Local boards</span></div>
+ {{ else }}
+ <div id="boardGridHeader" style="grid-column: 1 / 4;"><span style="font-size: 1.5em;font-weight: bold;">Local boards</span></div>
+ {{ end }}
+ {{ range .page.Boards }}
+ <div class="boardGridCell"><a href="{{.Location}}"><b>/{{.Name}}/</b> - {{.PrefName}} {{ if not .Restricted }} [NSFW] {{ end }}</a></div>
+ {{ end }}
+ {{ if gt $l 2 }}
+ {{ range .page.BoardRemainer }}
+ <div class="boardGridCell"></div>
+ {{ end }}
+ {{ end }}
+ </div>
+ </div>
+ {{ end }}
+
+ {{ if .page.NewsItems }}
+ <div class="newsbox" style="margin-top:50px;">
+ <h2><a href="/news">{{ .page.PreferredUsername }} News</a></h2>
+ {{ range $i, $e := .page.NewsItems }}
+ <div class="newsbox-news">
+ <h3><a href="/news/{{.Time}}">{{unixtoreadable $e.Time}} - {{$e.Title}}</a>{{ if $.Board.ModCred }} <a href="/{{ $.Key }}/newsdelete/{{ $e.Time }}">[Delete] </a>{{end}}</h3>
+ <br>
+
+ <p>{{$e.Content}}</p>
+ </div>
+ {{ end }}
+ </div>
+ {{ end }}
+
+ <div class="box2" style="margin-top:50px;">
+ <h4 style="margin-bottom:5px;">Current known instances</h4>
+ <span>(always use a proxy)</span>
+ <table style="text-align: left; margin: 25px;">
+ {{ range .page.InstanceIndex }}
+ <tr>
+ <td><a href="{{ .Id }}">{{ .Id }}</a></td>
+ </tr>
+ {{ end }}
+ </table>
+ </div>
+</div>
diff --git a/views/js/footerscript.js b/views/js/footerscript.js
new file mode 100644
index 0000000..69e56e7
--- /dev/null
+++ b/views/js/footerscript.js
@@ -0,0 +1,44 @@
+var imgs = document.querySelectorAll('#img');
+var imgArray = [].slice.call(imgs);
+
+imgArray.forEach(function(img, i){
+ img.addEventListener("click", function(e){
+ var id = img.getAttribute("id");
+ var media = document.getElementById("media-" + id);
+ var sensitive = document.getElementById("sensitive-" + id);
+
+ if(img.getAttribute("enlarge") == "0")
+ {
+ var attachment = img.getAttribute("attachment");
+ img.setAttribute("enlarge", "1");
+ img.setAttribute("style", "float: left; margin-right: 10px; cursor: pointer;");
+ img.src = attachment;
+ }
+ else
+ {
+ var preview = img.getAttribute("preview");
+ img.setAttribute("enlarge", "0");
+ if(img.getAttribute("main") == 1)
+ {
+ img.setAttribute("style", "float: left; margin-right: 10px; max-width: 250px; max-height: 250px; cursor: pointer;");
+ img.src = preview;
+ }
+ else
+ {
+ img.setAttribute("style", "float: left; margin-right: 10px; max-width: 125px; max-height: 125px; cursor: pointer;");
+ img.src = preview;
+ }
+ }
+ });
+});
+
+
+function viewLink(board, actor) {
+ var posts = document.querySelectorAll('#view');
+ var postsArray = [].slice.call(posts);
+
+ postsArray.forEach(function(p, i){
+ var id = p.getAttribute("post");
+ p.href = "/" + board + "/" + shortURL(actor, id);
+ });
+}
diff --git a/views/js/posts.js b/views/js/posts.js
new file mode 100644
index 0000000..87f6228
--- /dev/null
+++ b/views/js/posts.js
@@ -0,0 +1,344 @@
+function startNewPost(){
+ var el = document.getElementById("newpostbtn");
+ el.style="display:none;";
+ el.setAttribute("state", "1");
+ document.getElementById("newpost").style = "";
+ document.getElementById("stopTablePost").style = "display:unset;";
+ sessionStorage.setItem("newpostState", true);
+}
+
+function stopNewPost(){
+ var el = document.getElementById("newpostbtn");
+ el.style="display:block;margin-bottom:100px;";
+ el.setAttribute("state", "0");
+ document.getElementById("newpost").style = "display: none;";
+ sessionStorage.setItem("newpostState", false);
+}
+
+function shortURL(actorName, url)
+{
+ re = /.+\//g;
+ temp = re.exec(url);
+
+ var output;
+
+ if(stripTransferProtocol(temp[0]) == stripTransferProtocol(actorName) + "/")
+ {
+ var short = url.replace("https://", "");
+ short = short.replace("http://", "");
+ short = short.replace("www.", "");
+
+ var re = /^.{3}/g;
+
+ var u = re.exec(short);
+
+ re = /\w+$/g;
+
+ output = re.exec(short);
+ }else{
+ var short = url.replace("https://", "");
+ short = short.replace("http://", "");
+ short = short.replace("www.", "");
+
+ var re = /^.{3}/g;
+
+ var u = re.exec(short);
+
+ re = /\w+$/g;
+
+ u = re.exec(short);
+
+ str = short.replace(/\/+/g, " ");
+
+ str = str.replace(u, " ").trim();
+
+ re = /(\w|[!@#$%^&*<>])+$/;
+
+ v = re.exec(str);
+
+ output = "f" + v[0] + "-" + u
+ }
+
+ return output;
+}
+
+function getBoardId(url)
+{
+ var re = /\/([^/\n]+)(.+)?/gm;
+ var matches = re.exec(url);
+ return matches[1];
+}
+
+function convertContent(actorName, content, opid)
+{
+ var re = /(>>)(https?:\/\/)?(www\.)?.+\/\w+/gm;
+ var match = content.match(re);
+ var newContent = content;
+ if(match)
+ {
+ match.forEach(function(quote, i){
+ var link = quote.replace('>>', '');
+ var isOP = "";
+ if(link == opid)
+ {
+ isOP = " (OP)";
+ }
+
+ var q = link;
+
+ if(document.getElementById(link + "-content") != null) {
+ q = document.getElementById(link + "-content").innerText;
+ q = q.replaceAll('>', '/\>');
+ q = q.replaceAll('"', '');
+ q = q.replaceAll("'", "");
+ }
+ newContent = newContent.replace(quote, '<a class="reply" title="' + q + '" href="'+ (actorName) + "/" + shortURL(actorName, opid) + '#' + shortURL(actorName, link) + '";">>>' + shortURL(actorName, link) + isOP + '</a>');
+
+ });
+ }
+
+ re = /^(\s+)?>.+/gm;
+
+ match = newContent.match(re);
+ if(match)
+ {
+ match.forEach(function(quote, i) {
+
+ newContent = newContent.replace(quote, '<span class="quote">' + quote + '</span>');
+ });
+ }
+
+ return newContent.replaceAll('/\>', '>');
+}
+
+function convertContentNoLink(actorName, content, opid)
+{
+ var re = /(>>)(https?:\/\/)?(www\.)?.+\/\w+/gm;
+ var match = content.match(re);
+ var newContent = content;
+ if(match)
+ {
+ match.forEach(function(quote, i){
+ var link = quote.replace('>>', '');
+ var isOP = "";
+ if(link == opid)
+ {
+ isOP = " (OP)";
+ }
+
+ var q = link;
+
+ if(document.getElementById(link + "-content") != null) {
+ q = document.getElementById(link + "-content").innerText;
+ }
+
+ newContent = newContent.replace(quote, '>>' + shortURL(actorName, link) + isOP);
+ });
+ }
+ newContent = newContent.replaceAll("'", "");
+ return newContent.replaceAll('"', '');
+}
+
+function closeReply()
+{
+ document.getElementById("reply-box").style.display = "none";
+ document.getElementById("reply-comment").value = "";
+
+ sessionStorage.setItem("element-closed-reply", true);
+}
+
+function closeReport()
+{
+ document.getElementById("report-box").style.display = "none";
+ document.getElementById("report-comment").value = "";
+
+ sessionStorage.setItem("element-closed-report", true);
+}
+
+function quote(actorName, opid, id)
+{
+ sessionStorage.setItem("element-closed-reply", false);
+ var box = document.getElementById("reply-box");
+ var header = document.getElementById("reply-header");
+ var header_text = document.getElementById("reply-header-text");
+ var comment = document.getElementById("reply-comment");
+ var inReplyTo = document.getElementById("inReplyTo-box");
+
+ var w = window.innerWidth / 2 - 200;
+ var h = 300; //document.getElementById(id + "-content").offsetTop - 348;
+
+ const boxStyle = "top: " + h + "px; left: " + w + "px;";
+ box.setAttribute("style", boxStyle);
+ sessionStorage.setItem("element-reply-style", boxStyle);
+ sessionStorage.setItem("reply-top", h);
+ sessionStorage.setItem("reply-left", w);
+
+
+ if (inReplyTo.value != opid)
+ comment.value = "";
+
+ header_text.innerText = "Replying to Thread No. " + shortURL(actorName, opid);
+ inReplyTo.value = opid;
+ sessionStorage.setItem("element-reply-actor", actorName);
+ sessionStorage.setItem("element-reply-id", inReplyTo.value);
+
+ if(id != "reply")
+ comment.value += ">>" + id + "\n";
+ sessionStorage.setItem("element-reply-comment", comment.value);
+
+ dragElement(header);
+}
+
+function report(actorName, id)
+{
+ sessionStorage.setItem("element-closed-report", false);
+ var box = document.getElementById("report-box");
+ var header = document.getElementById("report-header");
+ var comment = document.getElementById("report-comment");
+ var inReplyTo = document.getElementById("report-inReplyTo-box");
+
+ var w = window.innerWidth / 2 - 200;
+ var h = 300; //document.getElementById(id + "-content").offsetTop - 348;
+
+ const boxStyle = "top: " + h + "px; left: " + w + "px;";
+ box.setAttribute("style", boxStyle);
+ sessionStorage.setItem("element-report-style", boxStyle);
+ sessionStorage.setItem("report-top", h);
+ sessionStorage.setItem("report-left", w);
+
+ header.innerText = "Report Post No. " + shortURL(actorName, id);
+ inReplyTo.value = id;
+ sessionStorage.setItem("element-report-actor", actorName);
+ sessionStorage.setItem("element-report-id", id);
+
+ dragElement(header);
+}
+
+var pos1, pos2, pos3, pos4;
+var elmnt;
+
+function closeDragElement(e) {
+ // stop moving when mouse button is released:
+ document.onmouseup = null;
+ document.onmousemove = null;
+ sessionStorage.setItem("eventhandler", false);
+}
+
+function elementDrag(e) {
+ e = e || window.event;
+ e.preventDefault();
+ // calculate the new cursor position:
+ pos1 = pos3 - e.clientX;
+ pos2 = pos4 - e.clientY;
+ pos3 = e.clientX;
+ pos4 = e.clientY;
+ sessionStorage.setItem("pos1", pos1);
+ sessionStorage.setItem("pos2", pos2);
+ sessionStorage.setItem("pos3", pos3);
+ sessionStorage.setItem("pos4", pos4);
+
+ // set the element's new position:
+ elmnt.parentElement.style.top = (elmnt.parentElement.offsetTop - pos2) + "px";
+ elmnt.parentElement.style.left = (elmnt.parentElement.offsetLeft - pos1) + "px";
+ if(elmnt.id.startsWith("report")){
+ sessionStorage.setItem("report-top", elmnt.parentElement.style.top);
+ sessionStorage.setItem("report-left", elmnt.parentElement.style.left);
+ }else if(elmnt.id.startsWith("reply")){
+ sessionStorage.setItem("reply-top", elmnt.parentElement.style.top);
+ sessionStorage.setItem("reply-left", elmnt.parentElement.style.left);
+ }
+}
+
+function dragMouseDown(e) {
+ e = e || window.event;
+ e.preventDefault();
+
+ // get the mouse cursor position at startup:
+ pos3 = e.clientX;
+ pos4 = e.clientY;
+ sessionStorage.setItem("pos3", pos3);
+ sessionStorage.setItem("pos4", pos4);
+
+ elmnt = e.currentTarget;
+
+ // call a function whenever the cursor moves:
+ document.onmouseup = closeDragElement;
+ document.onmousemove = elementDrag;
+ sessionStorage.setItem("eventhandler", true);
+
+}
+
+function dragElement(elmnt) {
+ elmnt.onmousedown = dragMouseDown;
+}
+
+const stateLoadHandler = function(event){
+ pos1 = parseInt(sessionStorage.getItem("pos1"));
+ pos2 = parseInt(sessionStorage.getItem("pos2"));
+ pos3 = parseInt(sessionStorage.getItem("pos3"));
+ pos4 = parseInt(sessionStorage.getItem("pos4"));
+
+ if(sessionStorage.getItem("element-closed-report") === "false"){
+ var box = document.getElementById("report-box");
+ var header = document.getElementById("report-header");
+ var comment = document.getElementById("report-comment");
+ var inReplyTo = document.getElementById("report-inReplyTo-box");
+
+ header.onmousedown = dragMouseDown;
+ inReplyTo.value = parseInt(sessionStorage.getItem("element-report-id"));
+ header.innerText = "Report Post No. " + shortURL(sessionStorage.getItem("element-report-actor"), sessionStorage.getItem("element-report-id"));
+ comment.value = sessionStorage.getItem("element-report-comment");
+
+ box.setAttribute("style", sessionStorage.getItem("element-report-style"));
+
+ box.style.top = sessionStorage.getItem("report-top");
+ box.style.left = sessionStorage.getItem("report-left");
+
+ if(sessionStorage.getItem("eventhandler") === "true"){
+ elmnt = header;
+ document.onmouseup = closeDragElement;
+ document.onmousemove = elementDrag;
+ }else{
+ document.onmouseup = null;
+ document.onmousemove = null;
+ }
+ }
+ if(sessionStorage.getItem("element-closed-reply") === "false"){
+ var box = document.getElementById("reply-box");
+ var header = document.getElementById("reply-header");
+ var header_text = document.getElementById("reply-header-text");
+ var comment = document.getElementById("reply-comment");
+ var inReplyTo = document.getElementById("inReplyTo-box");
+
+ header.onmousedown = dragMouseDown;
+ inReplyTo.value = parseInt(sessionStorage.getItem("element-reply-id"));
+ header_text.innerText = "Replying to Thread No. " + shortURL(sessionStorage.getItem("element-reply-actor"), sessionStorage.getItem("element-reply-id"));
+ comment.value = sessionStorage.getItem("element-reply-comment");
+
+ pos1 = parseInt(sessionStorage.getItem("pos1"));
+ pos2 = parseInt(sessionStorage.getItem("pos2"));
+ pos3 = parseInt(sessionStorage.getItem("pos3"));
+ pos4 = parseInt(sessionStorage.getItem("pos4"));
+
+ box.setAttribute("style", sessionStorage.getItem("element-reply-style"));
+
+ box.style.top = sessionStorage.getItem("reply-top");
+ box.style.left = sessionStorage.getItem("reply-left");
+
+ if(sessionStorage.getItem("eventhandler") === "true"){
+ elmnt = header;
+ document.onmouseup = closeDragElement;
+ document.onmousemove = elementDrag;
+ }else{
+ document.onmouseup = null;
+ document.onmousemove = null;
+ }
+ }
+};
+
+document.addEventListener("DOMContentLoaded", stateLoadHandler, false);
+
+function stripTransferProtocol(value){
+ var re = /(https:\/\/|http:\/\/)?(www.)?/;
+ return value.replace(re, "");
+}
diff --git a/views/js/themes.js b/views/js/themes.js
new file mode 100644
index 0000000..ccdd277
--- /dev/null
+++ b/views/js/themes.js
@@ -0,0 +1,40 @@
+function setCookie(key, value, age) {
+ document.cookie = key + "=" + encodeURIComponent(value) + ";sameSite=strict;max-age=" + (60 * 60 * 24 * age) + ";path=/";
+}
+
+function getCookie(key) {
+ if (document.cookie.length != 0) {
+ return document.cookie.split('; ').find(row => row.startsWith(key)).split('=')[1];
+ }
+ return "";
+}
+
+function setTheme(name) {
+ for (let i = 0, tags = document.getElementsByTagName("link"); i < tags.length; i++) {
+ if (tags[i].type === "text/css" && tags[i].title) {
+ tags[i].disabled = !(tags[i].title === name);
+ }
+ }
+
+ setCookie("theme", name, 3650);
+}
+
+function applyTheme() {
+ // HACK: disable all of the themes first. this for some reason makes things work.
+ for (let i = 0, tags = document.getElementsByTagName("link"); i < tags.length; i++) {
+ if (tags[i].type === "text/css" && tags[i].title) {
+ tags[i].disabled = true;
+ }
+ }
+ let theme = getCookie("theme") || "default";
+ setTheme(theme);
+
+ // reflect this in the switcher
+ let switcher = document.getElementById("themeSwitcher");
+ for(var i = 0; i < switcher.options.length; i++) {
+ if (switcher.options[i].value === theme) {
+ switcher.selectedIndex = i;
+ break;
+ }
+ }
+}
diff --git a/views/js/timer.js b/views/js/timer.js
new file mode 100644
index 0000000..d8be9de
--- /dev/null
+++ b/views/js/timer.js
@@ -0,0 +1,38 @@
+var timerCount;
+var timerToggle = false;
+var timer;
+const contentLoadHandler = function(event){
+ timerToggle = !!document.getElementById("autoreload-checkbox").checked;
+ if(timerToggle){
+ timerCount = 45;
+ document.getElementById("autoreload-countdown").innerHTML = "45";
+ document.getElementById("autoreload-countdown").style.visibility = "visible";
+ timer = setInterval(timerFunction, 1000);
+ document.removeEventListener("DOMContentLoaded", contentLoadHandler, false);
+ }
+};
+
+document.addEventListener("DOMContentLoaded", contentLoadHandler, false);
+
+function timerFunction(){
+ timerCount--;
+ document.getElementById("autoreload-countdown").innerHTML = timerCount;
+ if(timerCount <= 0){
+ document.getElementById("autoreload-countdown").innerHTML = "Refreshing...";
+ clearInterval(timer);
+ location.reload();
+ }
+}
+
+function autoTimer(){
+ timerToggle = !timerToggle;
+ if(timerToggle === true){
+ timerCount = 45;
+ document.getElementById("autoreload-countdown").innerHTML = "45";
+ document.getElementById("autoreload-countdown").style.visibility = "visible";
+ timer = setInterval(timerFunction, 1000);
+ }else{
+ clearInterval(timer);
+ document.getElementById("autoreload-countdown").style.visibility = "hidden";
+ }
+}
diff --git a/views/layouts/main.html b/views/layouts/main.html
new file mode 100644
index 0000000..4cd48f9
--- /dev/null
+++ b/views/layouts/main.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <meta name="keywords" content="Federated Imageboard based on Activtypub">
+ <meta property="og:locale" content="en_US" />
+ <meta property="og:type" content="website" />
+ <link rel="icon" type="image/png" href="/static/favicon.png">
+ {{ if gt (len .page.ThemeCookie) 0 }}
+ <link rel="stylesheet" type="text/css" href="/static/css/themes/{{.page.ThemeCookie}}.css" title="selected theme">
+ {{ else }}
+ <link rel="stylesheet" type="text/css" href="/static/css/themes/default.css" title="default">
+ {{ end }}
+ {{ range .page.Themes }}
+ <link rel="alternate stylesheet" type="text/css" href="/static/css/themes/{{.}}.css" title="{{.}}" disabled>
+ {{ end }}
+
+ {{ template "header" .page }}
+ </head>
+ <body {{ if not .page.Board.Restricted }}class="nsfw"{{ end }} onload="applyTheme()">
+ <ul id="boardlinks">
+ {{ $l := len .page.Boards }}
+ <li>[<a href="/">Home</a>]</li>
+ {{range $i, $e := .page.Boards}}
+ {{ if eq (sub $l 1) 0 }}
+ <li>[ <a href="{{.Location}}">{{$e.Name}} </a>]</li>
+ {{ else if eq $i 0 }}
+ <li>[<a href="{{.Location}}">{{$e.Name}} </a>/</li>
+ {{ else if eq $i (sub $l 1) }}
+ <li><a href="{{.Location}}">{{$e.Name}}</a>]</li>
+ {{ else }}
+ <li><a href="{{.Location}}">{{$e.Name}} </a>/</li>
+ {{ end }}
+ {{ end }}
+ </ul>
+ {{ if .page.Board.ModCred }}
+ {{ if eq .page.Board.ModCred .page.Board.Domain .page.Board.Actor.Id }}
+ <span style="float: right;">[<a href="/{{ .page.Key }}/{{ .page.Board.Name }}">Manage Board</a>]</span>
+ {{ end }}
+ {{ end }}
+
+ {{ embed }}
+
+ {{ template "partials/footer" .page }}
+
+ <script src="/static/js/themes.js"></script>
+ {{ template "scripts" .page }}
+ </body>
+</html>
diff --git a/views/npost.html b/views/npost.html
new file mode 100644
index 0000000..5a382ed
--- /dev/null
+++ b/views/npost.html
@@ -0,0 +1,66 @@
+{{ define "header" }}
+<title>/{{ .Board.Name }}/ - {{ .PostId }}</title>
+<meta name="description" content="{{ (index .Posts 0).Content }}">
+<meta property="og:url" content="{{ (index .Posts 0).Id }}">
+<meta property="og:site_name" content="{{ .Instance.PreferredUsername }}" />
+
+<meta property="og:title" content="{{ (index .Posts 0).Name }}">
+<meta property="og:description" content="{{ (index .Posts 0).Content }}">
+
+<meta name="twitter:title" content="{{ (index .Posts 0).Name }}">
+<meta name="twitter:description" content="{{ (index .Posts 0).Content }}">
+<meta name="twitter:card" content="summary_large_image">
+
+{{ if (index .Posts 0).Preview }}
+<meta property="og:image" content="{{ (index .Posts 0).Preview.Href }}" />
+<meta name="twitter:image" content="{{ (index .Posts 0).Preview.Href }}" />
+{{ end }}
+{{ end }}
+
+{{ template "partials/top" .page }}
+
+{{ $board := .Board }}
+
+<hr>
+<ul id="navlinks">
+ <li>[<a href="/{{ $board.Name }}">Return</a>]</li>
+ <li>[<a href="/{{ $board.Name }}/catalog">Catalog</a>]</li>
+ <li>[<a href="#bottom">Bottom</a>]</li>
+ <li>[<a href="javascript:location.reload()">Refresh</a>]</li>
+</ul>
+<hr>
+
+{{ template "partials/posts" .page }}
+
+<hr>
+
+<table id="threadfooter"><tr>
+ <td>
+ <ul id="navlinks">
+ <li>[<a href="/{{ $board.Name }}">Return</a>]</li>
+ <li>[<a href="/{{ $board.Name }}/catalog">Catalog</a>]</li>
+ <li>[<a id="bottom" href="#top">Top</a>]</li>
+ <li>[<a href="javascript:location.reload()">Refresh</a>]</li>
+ <li><input id="autoreload-checkbox" type="checkbox" onclick="autoTimer()"> Auto refresh <span id="autoreload-countdown" style="visibility: hidden;">0</span></li>
+ </ul>
+ </td>
+
+ {{ if eq (index .page.Posts 0).Type "Note" }}
+ <td style="text-align: center;">
+ <span>[<a id="reply-content" href="javascript:quote('{{ $board.Actor.Id }}', '{{ (index .page.Posts 0).Id }}', 'reply')">Post a Reply</a>]</span>
+ </td>
+ {{ end }}
+
+ <td>
+ {{ $replies := (index .page.Posts 0).Replies }}
+ <span id="threadStats" data-total="{{ $replies.TotalItems }}" data-imgs="{{ $replies.TotalImgs }}">{{ $replies.TotalItems }} / {{ $replies.TotalImgs }}</span>
+ </td>
+</tr></table>
+
+<hr>
+
+{{ template "partials/bottom" .page }}
+
+{{ define "scripts" }}
+{{ template "partials/postscripts" . }}
+{{ end }}
diff --git a/views/nposts.html b/views/nposts.html
new file mode 100644
index 0000000..ae5a9b6
--- /dev/null
+++ b/views/nposts.html
@@ -0,0 +1,70 @@
+{{ define "header" }}
+<title>{{ .Title }}</title>
+<meta name="description" content="{{ .Board.Summary }}">
+<meta property="og:url" content="{{ .Board.Actor.Id }}">
+<meta property="og:site_name" content="{{ .Instance.PreferredUsername }}" />
+
+<meta property="og:title" content="{{ .Title }}">
+<meta property="og:description" content="{{ .Board.Summary }}">
+
+<meta name="twitter:title" content="{{ .Title }}">
+<meta name="twitter:description" content="{{ .Board.Summary }}">
+<meta name="twitter:card" content="summary_large_image">
+{{ end }}
+
+{{ template "partials/top" .page }}
+
+{{ $board := .page.Board }}
+<hr>
+<ul id="navlinks">
+ <li>[<a href="/{{ $board.Name }}/catalog">Catalog</a>]</li>
+ <!-- TODO: showArchive function needs to be fixed
+ \{\{ if showArchive }}
+ <li>[<a href="/{{ $board.Name }}/archive">Archive</a>]</li>
+ \{\{ end }}
+ -->
+ <li>[<a href="#bottom">Bottom</a>]</li>
+ <li>[<a href="javascript:location.reload()">Refresh</a>]</li>
+</ul>
+
+{{ template "partials/posts" .page }}
+
+<hr>
+
+<ul id="navlinks">
+ <li>[<a href="/{{ $board.Name }}/catalog">Catalog</a>]</li>
+ <!-- TODO: showArchive function needs to be fixed
+ \{\{ if showArchive }}
+ <li>[<a href="/{{ $board.Name }}/archive">Archive</a>]</li>
+ \{\{ end }}
+ -->
+ <li>[<a href="#top" id="bottom">Top</a>]</li>
+ <li>[<a href="javascript:location.reload()">Refresh</a>]</li>
+</ul>
+
+<hr>
+{{ if gt .page.TotalPage 0 }}
+{{ $totalPage := .page.TotalPage }}
+<ul style="float: right; margin: 0; padding: 0; display: inline">
+ {{ $page := .page.CurrentPage }}
+ {{ if gt $page 0 }}
+ <li style="display: inline">[<a href="/{{ $board.Name }}?page={{ sub $page 1 }}">&lt;</a>]</li>
+ {{ end }}
+ {{ range $i, $e := .page.Pages }}
+ {{ if eq $i $page}}
+ <li style="display: inline">[<a href="/{{ $board.Name }}?page={{ $i }}"><b>{{ $i }}</b></a>]</li>
+ {{ else }}
+ <li style="display: inline">[<a href="/{{ $board.Name }}?page={{ $i }}">{{ $i }}</a>]</li>
+ {{ end }}
+ {{ end }}
+ {{ if lt .page.CurrentPage .page.TotalPage }}
+ <li style="display: inline">[<a href="/{{ $board.Name }}?page={{ add $page 1 }}">&gt;</a>]</li>
+ {{ end }}
+</ul>
+{{ end }}
+
+{{ template "partials/bottom" .page }}
+
+{{ define "scripts" }}
+{{ template "partials/postscripts" . }}
+{{ end }}
diff --git a/views/partials/bottom.html b/views/partials/bottom.html
new file mode 100644
index 0000000..35e8c4a
--- /dev/null
+++ b/views/partials/bottom.html
@@ -0,0 +1,48 @@
+<div id="reply-box" class="popup-box" style="display: none;">
+ <div id="reply-header">
+ <span id="reply-header-text">...</span>
+ <div id="reply-close" style="display: inline-block; float: right;"><a href="javascript:closeReply()">[X]</a></div>
+ </div>
+ <form onsubmit="sessionStorage.setItem('element-closed-reply', true)" id="reply-post" action="/post" method="post" enctype="multipart/form-data">
+ <input id="reply-name" name="name" type="text" placeholder="Name" maxlength="100">
+ <input id="reply-options" name="options" type="text" placeholder="Options" maxlength="100">
+ <textarea id="reply-comment" name="comment" maxlength="2000" oninput="sessionStorage.setItem('element-reply-comment', document.getElementById('reply-comment').value)"></textarea>
+ <input id="reply-file" name="file" type="file">
+ <input id="reply-submit" type="submit" value="Reply" style="float: right;">
+ <input type="hidden" id="inReplyTo-box" name="inReplyTo" value="{{ .Board.InReplyTo }}">
+ <input type="hidden" id="sendTo" name="sendTo" value="{{ .Board.To }}">
+ <input type="hidden" id="boardName" name="boardName" value="{{ .Board.Name }}">
+ <input type="hidden" id="captchaCode" name="captchaCode" value="{{ .Board.CaptchaCode }}">
+ <input type="hidden" id="returnTo" name="returnTo" value="{{ .ReturnTo }}"><br>
+ <input type="checkbox" name="sensitive"><span>Mark attachment as sensitive</span><br>
+ <div style="width: 202px; margin: 0 auto; padding-top: 12px;">
+ <label for="captcha">Captcha:</label><br>
+ <input style="display: inline-block;" type="text" id="captcha" name="captcha" autocomplete="off"><br>
+ </div>
+ <div style="width: 230px; margin: 0 auto;">
+ <img src="{{ .Board.Captcha }}">
+ </div>
+ </form>
+</div>
+
+<div id="report-box" class="popup-box" style="display: none;">
+ <div id="report-header" style="text-align: center; display: inline-block; z-index: 0; cursor: move;"></div><div id="report-close" style="display: inline-block; float: right;"><a href="javascript:closeReport()">[X]</a></div>
+ <form onsubmit="sessionStorage.setItem('element-closed-report', true)" id="report-post" action="/report" method="post">
+ <label for="comment">Reason:</label><br>
+ <textarea id="report-comment" name="comment" rows="12" cols="54" style="width: 396px;" maxlength="100" oninput="sessionStorage.setItem('element-report-comment', document.getElementById('report-comment').value)"></textarea>
+ <br>
+ <input id="report-submit" type="submit" value="Report" style="float: right;">
+ <input type="hidden" id="report-inReplyTo-box" name="id" value="{{ .Board.InReplyTo }}">
+ <input type="hidden" id="sendTo" name="sendTo" value="{{ .Board.To }}">
+ <input type="hidden" id="boardName" name="board" value="{{ .Board.Name }}">
+ <input type="hidden" name="close" value="0">
+ <input type="hidden" id="captchaCode" name="captchaCode" value="{{ .Board.CaptchaCode }}">
+ <div style="width: 202px; margin: 0 auto; padding-top: 12px;">
+ <label for="captcha">Captcha:</label><br>
+ <input style="display: inline-block;" type="text" id="captcha" name="captcha" autocomplete="off"><br>
+ </div>
+ <div style="width: 230px; margin: 0 auto;">
+ <img src="{{ .Board.Captcha }}">
+ </div>
+ </form>
+</div>
diff --git a/views/partials/footer.html b/views/partials/footer.html
new file mode 100644
index 0000000..efbde4f
--- /dev/null
+++ b/views/partials/footer.html
@@ -0,0 +1,13 @@
+<div align="center" style="width: 500px; margin:0 auto; margin-top: 50px;">
+ [<a href="/">Home</a>] [<a href="/static/rules.html">Rules</a>] [<a href="/static/faq.html">FAQ</a>]
+ <p>All trademarks and copyrights on this page are owned by their respective parties.</p>
+</div>
+
+<div style="float: right; margin-bottom: 25px;">
+ Theme:
+ <select id="themeSwitcher" onchange="setTheme(this.options[this.selectedIndex].value)">
+ {{ range .Themes }}
+ <option value="{{.}}">{{.}}</option>
+ {{ end }}
+ </select>
+</div>
diff --git a/views/partials/posts.html b/views/partials/posts.html
new file mode 100644
index 0000000..f82d2d5
--- /dev/null
+++ b/views/partials/posts.html
@@ -0,0 +1,122 @@
+{{ $board := .Board }}
+{{ $len := len .Posts }}
+{{ range .Posts }}
+{{ $thread := . }}
+{{ $opId := .Id }}
+{{ if eq $board.InReplyTo "" }}
+<hr>
+{{ end }}
+<div style="overflow: auto;">
+ <div id="{{ short $board.Actor.Outbox .Id }}" style="overflow: visible; margin-bottom: 12px;">
+ {{ if eq $board.ModCred $board.Domain $board.Actor.Id }}
+ [<a href="/delete?id={{ .Id }}&board={{ $board.Actor.Name }}">Delete Post</a>]
+ {{ end }}
+ {{ if .Attachment }}
+ {{ if eq $board.ModCred $board.Domain $board.Actor.Id }}
+ [<a href="/banmedia?id={{ .Id }}&board={{ $board.Actor.Name }}">Ban Media</a>]
+ [<a href="/deleteattach?id={{ .Id }}&board={{ $board.Actor.Name }}">Delete Attachment</a>]
+ [<a href="/marksensitive?id={{ .Id }}&board={{ $board.Actor.Name }}">Mark Sensitive</a>]
+ {{ end }}
+ <span style="display: block;">File: <a id="{{ .Id }}-img" href="{{ proxy (index .Attachment 0).Href}}">{{ shortImg (index .Attachment 0).Name }}</a><span id="{{ .Id }}-size"> ({{ convertSize (index .Attachment 0).Size }})</span></span>
+ <div id="hide-{{ .Id }}" style="display: none;">[Hide]</div>
+ <div id="sensitive-{{ .Id }}" style="display: none;"><div style="position: relative; text-align: center;"><img id="sensitive-img-{{ .Id }}" style="float: left; margin-right: 10px; margin-bottom: 10px; max-width: 250px; max-height: 250px;" src="/static/sensitive.png"><div id="sensitive-text-{{ .Id }}" style="width: 240px; position: absolute; margin-top: 110px; padding: 5px; background-color: black; color: white; cursor: default; ">NSFW Content</div></div></div>
+ <div id="media-{{ .Id }}">{{ parseAttachment . false }}</div>
+ <script>
+ media = document.getElementById("media-{{ .Id }}")
+ if(({{ .Sensitive }} && {{ $board.Actor.Restricted }}) || ({{ isOnion .Id }} && !{{ isOnion $board.Domain }})){
+ sensitive = document.getElementById("sensitive-{{ .Id }}")
+ hide = document.getElementById("hide-{{ .Id }}")
+ sensitive.onclick = function(){document.getElementById("media-{{ .Id }}").style="display: block;"; document.getElementById("sensitive-{{ .Id }}").style="display: none;"; document.getElementById("hide-{{ .Id }}").style="display: block; cursor: pointer;"}
+ hide.onclick = function(){document.getElementById("media-{{ .Id }}").style="display: none;"; document.getElementById("sensitive-{{ .Id }}").style="display: block;"; document.getElementById("hide-{{ .Id }}").style="display: none;"}
+ sensitive.style = "display: block"
+ media.style = "display: none;"
+ }
+
+ if({{ isOnion .Id }} && !{{ isOnion $board.Domain }}) {
+ sensitive = document.getElementById("sensitive-{{ .Id }}")
+ document.getElementById("sensitive-img-{{ .Id }}").src = "/static/onion.png"
+ document.getElementById("sensitive-text-{{ .Id }}").innerText = "Tor Instance"
+ hide = document.getElementById("hide-{{ .Id }}")
+ sensitive.onclick = function(){document.getElementById("media-{{ .Id }}").style="display: block;"; document.getElementById("sensitive-{{ .Id }}").style="display: none;"; document.getElementById("hide-{{ .Id }}").style="display: block; cursor: pointer;"}
+ hide.onclick = function(){document.getElementById("media-{{ .Id }}").style="display: none;"; document.getElementById("sensitive-{{ .Id }}").style="display: block;"; document.getElementById("hide-{{ .Id }}").style="display: none;"}
+ sensitive.style = "display: block"
+ media.style = "display: none;"
+ }
+ </script>
+ {{ end }}
+ <span class="subject"><b>{{ .Name }}</b></span>
+ <span class="name"><b>{{ if .AttributedTo }} {{.AttributedTo }} {{ else }} Anonymous {{ end }}</b></span>
+ <span class="tripcode"> {{ .TripCode }} </span>
+ <span class="timestamp" data-utc="{{.Published | timeToUnix}}">{{ .Published | timeToReadableLong }} <a id="{{ .Id }}-anchor" href="/{{ $board.Name }}/{{ short $board.Actor.Outbox $opId }}#{{ short $board.Actor.Outbox .Id }}">No.</a> <a id="{{ .Id }}-link" title="{{ .Id }}" {{ if eq .Type "Note" }} href="javascript:quote('{{ $board.Actor.Id }}', '{{ $opId }}', '{{ .Id }}')" {{ end }}>{{ short $board.Actor.Outbox .Id }}</a> {{ if ne .Type "Tombstone" }}[<a href="javascript:report('{{ $board.Actor.Id }}', '{{ .Id }}')">Report</a>]{{ end }}</span>
+ <p id="{{ .Id }}-content" style="white-space: pre-wrap; margin: 10px 30px 10px 30px;">{{ parseContent $board.Actor $opId .Content $thread }}</p>
+ {{ if .Replies }}
+ {{ $replies := .Replies }}
+ {{ if gt $replies.TotalItems 5 }}
+ {{ if gt $len 1 }}
+ <span>{{ $replies.TotalItems }} replies{{ if gt $replies.TotalImgs 0}} and {{ $replies.TotalImgs }} images{{ end }}, Click <a id="view" post="{{.Id}}" href="/{{ $board.Name }}/{{ short $board.Actor.Outbox .Id }}">here</a> to view all.</span>
+ {{ end }}
+ {{ end }}
+ {{ range $replies.OrderedItems }}
+ <div id="{{ short $board.Actor.Outbox .Id }}">
+ <div style="display: inline-block; overflow: auto;">
+ <div style="float: left; display: block; margin-right: 5px;">&gt;&gt;</div>
+ <div class="post" style="overflow: auto; padding: 5px; margin-bottom: 2px;">
+ {{ if eq $board.ModCred $board.Domain $board.Actor.Id }}
+ [<a href="/delete?id={{ .Id }}&board={{ $board.Actor.Name }}">Delete Post</a>]
+ {{ end }}
+ {{ if .Attachment }}
+ {{ if eq $board.ModCred $board.Domain $board.Actor.Id }}
+ [<a href="/banmedia?id={{ .Id }}&board={{ $board.Actor.Name }}">Ban Media</a>]
+ [<a href="/deleteattach?id={{ .Id }}&board={{ $board.Actor.Name }}">Delete Attachment</a>]
+ [<a href="/marksensitive?id={{ .Id }}&board={{ $board.Actor.Name }}">Mark Sensitive</a>]
+ {{ end }}
+ <span style="display: block;">File <a id="{{ .Id }}-img" href="{{ proxy (index .Attachment 0).Href}}">{{ shortImg (index .Attachment 0).Name }}</a> <span id="{{ .Id }}-size">({{ convertSize (index .Attachment 0).Size }})</span></span>
+ <div id="hide-{{ .Id }}" style="display: none;">[Hide]</div>
+ <div id="sensitive-{{ .Id }}" style="display: none;"><div style="position: relative; text-align: center;"><img id="sensitive-img-{{ .Id }}" style="float: left; margin-right: 10px; margin-bottom: 10px; max-width: 250px; max-height: 250px;" src="/static/sensitive.png"><div id="sensitive-text-{{ .Id }}" style="width: 240px; position: absolute; margin-top: 110px; padding: 5px; background-color: black; color: white; cursor: default; ">NSFW Content</div></div></div>
+ <div> </div>
+ <div id="media-{{ .Id }}" sensitive="0">{{ parseAttachment . false }}</div>
+ <script>
+ media = document.getElementById("media-{{ .Id }}")
+
+ if(({{ .Sensitive }} && {{ $board.Actor.Restricted }}) || {{ isOnion .Id }} && !{{ isOnion $board.Domain }}){
+ sensitive = document.getElementById("sensitive-{{ .Id }}")
+ hide = document.getElementById("hide-{{ .Id }}")
+ sensitive.onclick = function(){document.getElementById("media-{{ .Id }}").style="display: block;"; document.getElementById("sensitive-{{ .Id }}").style="display: none;"; document.getElementById("hide-{{ .Id }}").style="display: block; cursor: pointer;"}
+ hide.onclick = function(){document.getElementById("media-{{ .Id }}").style="display: none;"; document.getElementById("sensitive-{{ .Id }}").style="display: block;"; document.getElementById("hide-{{ .Id }}").style="display: none;"}
+ sensitive.style = "display: block"
+ media.style = "display: none;"
+ }
+
+ if({{ isOnion .Id }} && !{{ isOnion $board.Domain }}) {
+ sensitive = document.getElementById("sensitive-{{ .Id }}")
+ document.getElementById("sensitive-img-{{ .Id }}").src = "/static/onion.png"
+ document.getElementById("sensitive-text-{{ .Id }}").innerText = "Tor Instance"
+ hide = document.getElementById("hide-{{ .Id }}")
+ sensitive.onclick = function(){document.getElementById("media-{{ .Id }}").style="display: block;"; document.getElementById("sensitive-{{ .Id }}").style="display: none;"; document.getElementById("hide-{{ .Id }}").style="display: block; cursor: pointer;"}
+ hide.onclick = function(){document.getElementById("media-{{ .Id }}").style="display: none;"; document.getElementById("sensitive-{{ .Id }}").style="display: block;"; document.getElementById("hide-{{ .Id }}").style="display: none;"}
+ sensitive.style = "display: block"
+ media.style = "display: none;"
+ }
+ </script>
+ {{ end }}
+ <span class="subject"><b>{{ .Name }}</b></span>
+ <span class="name"><b>{{ if .AttributedTo }} {{.AttributedTo }} {{ else }} Anonymous {{ end }}</b></span>
+ <span class="tripcode"> {{ .TripCode }} </span>
+ <span class="timestamp" data-utc="{{ .Published | timeToUnix }}">{{ .Published | timeToReadableLong }} <a id="{{ .Id }}-anchor" href="/{{ $board.Name }}/{{ short $board.Actor.Outbox $opId }}#{{ short $board.Actor.Outbox .Id }}">No. </a><a id="{{ .Id }}-link" title="{{ .Id }}" {{ if eq .Type "Note" }} href="javascript:quote('{{ $board.Actor.Id }}', '{{ $opId }}', '{{ .Id }}')" {{ end }}>{{ short $board.Actor.Outbox .Id }}</a> {{ if ne .Type "Tombstone" }}[<a href="javascript:report('{{ $board.Actor.Id }}', '{{ .Id }}')">Report</a>]{{ end }}</span>
+ {{ $parentId := .Id }}
+ {{ if .Replies.OrderedItems }}
+ {{ range .Replies.OrderedItems }}
+
+ <!--TODO: fix parse reply link function with other routes mainly getactor-->
+ <span id="{{$parentId}}-replyto-{{.Id}}"><!-- fix \{\{ parseReplyLink $board.Actor.Id $opId .Id .Content }} --></span>
+ {{ end }}
+ {{ end }}
+ <p id="{{ .Id }}-content" style="white-space: pre-wrap; margin: 10px 30px 10px 30px;">{{ parseContent $board.Actor $opId .Content $thread }}</p>
+ </div>
+ </div>
+ </div>
+ {{ end }}
+ {{ end }}
+ </div>
+</div>
+{{ end }}
diff --git a/views/partials/postscripts.html b/views/partials/postscripts.html
new file mode 100644
index 0000000..f26e354
--- /dev/null
+++ b/views/partials/postscripts.html
@@ -0,0 +1,3 @@
+<script src="/static/js/posts.js"></script>
+<script src="/static/js/footerscript.js"></script>
+<script src="/static/js/timer.js"></script>
diff --git a/views/partials/top.html b/views/partials/top.html
new file mode 100644
index 0000000..7c03c36
--- /dev/null
+++ b/views/partials/top.html
@@ -0,0 +1,128 @@
+<div style="margin: 0 auto; width: 700px;">
+ <h1 style="text-align: center;">/{{ .Board.Name }}/ - {{ .Board.PrefName }}</h1>
+ <p style="text-align: center;">{{ .Board.Summary }}</p>
+ {{ $len := len .Posts }}
+ {{ if eq $len 0 }}
+ {{ if .Board.InReplyTo }}
+ <h3 id="newpostbtn" state="0" style="display: none; margin-bottom:100px;">[<a href="javascript:startNewPost()">Post a Reply</a>]</h3>
+ {{ else }}
+ <h3 id="newpostbtn" state="0" style="display: none; margin-bottom:100px;">[<a href="javascript:startNewPost()">Start a New Thread</a>]</h3>
+ {{ end }} <!-- end if inreplyto-->
+ <div id="newpost">
+ <form onsubmit="sessionStorage.setItem('element-closed-reply', true)" id="new-post" action="/post" method="post" enctype="multipart/form-data">
+ <table id="postForm">
+ <tr>
+ <tr>
+ <td><label for="name">Name:</label></td>
+ <td><input type="text" id="name" name="name" placeholder="Anonymous" maxlength="100">
+ <a id="stopTablePost" onclick="stopNewPost()">[X]</a>
+ </td>
+ </tr>
+ <tr>
+ <td><label for="options">Options:</label></td>
+ <td><input type="text" id="options" name="options" maxlength="100" style="margin-right:10px">{{ if .Board.InReplyTo }}<input type="submit" value="Post">{{ end }}</td>
+ </tr>
+ {{ if eq .Board.InReplyTo "" }}
+ <tr>
+ <td><label for="subject">Subject:</label></td>
+ <td><input type="text" id="subject" name="subject" maxlength="100" style="margin-right:10px"><input type="submit" value="Post"></td>
+ </tr>
+ {{ end }}
+ <tr>
+ <td><label for="comment">Comment:</label></td>
+ <td><textarea rows="10" cols="50" id="comment" name="comment" maxlength="2000"></textarea></td>
+ </tr>
+ <tr>
+ <td><label for="file">Image</label></td>
+ <td><input type="file" id="file" name="file" {{ if gt $len 1 }} required {{ else }} {{ if eq $len 0 }} required {{ end }} {{ end }} >
+ <br><input type="checkbox" name="sensitive">Mark sensitive</input></td>
+ </tr>
+ <tr>
+ <td><label for="captcha">Captcha:</label></td>
+ <td>
+ <div style="height: 65px; display: inline;">
+ <img src="{{ .Board.Captcha }}">
+ </div>
+ <input type="text" id="captcha" name="captcha" autocomplete="off">
+ </td>
+ </tr>
+ </table>
+
+ <input type="hidden" id="inReplyTo" name="inReplyTo" value="{{ .Board.InReplyTo }}">
+ <input type="hidden" id="sendTo" name="sendTo" value="{{ .Board.To }}">
+ <input type="hidden" id="boardName" name="boardName" value="{{ .Board.Name }}">
+ <input type="hidden" id="captchaCode" name="captchaCode" value="{{ .Board.CaptchaCode }}">
+ <input type="hidden" id="returnTo" name="returnTo" value="{{ .ReturnTo }}">
+ </form>
+ </div>
+
+ {{ else }} <!-- special case to distinquish Notes and Archived formatting -->
+
+ {{ if eq (index .Posts 0).Type "Note" }}
+ {{ if .Board.InReplyTo }}
+ <h3 id="newpostbtn" state="0" style="text-align: center; margin-top: 80px; display: none; margin-bottom:100px;">[<a href="javascript:startNewPost()">Post a Reply</a>]</h3>
+ {{ else }}
+ <h3 id="newpostbtn" state="0" style="text-align: center; margin-top: 80px; display: none; margin-bottom:100px;">[<a href="javascript:startNewPost()">Start a New Thread</a>]</h3>
+ {{ end }} <!-- end if inreplyto-->
+ {{ $len := len .Posts }}
+ <div id="newpost">
+ <form onsubmit="sessionStorage.setItem('element-closed-reply', true)" id="new-post" action="/post" method="post" enctype="multipart/form-data">
+ <table id="postForm">
+ <tr>
+ <tr>
+ <td><label for="name">Name:</label></td>
+ <td><input type="text" id="name" name="name" placeholder="Anonymous" maxlength="100">
+ <a id="stopTablePost" onclick="stopNewPost()">[X]</a>
+ </tr>
+ <tr>
+ <td><label for="options">Options:</label></td>
+ <td><input type="text" id="options" name="options" maxlength="100" style="margin-right:10px">{{ if .Board.InReplyTo }}<input type="submit" value="Post">{{ end }}</td>
+ </tr>
+ {{ if eq .Board.InReplyTo "" }}
+ <tr>
+ <td><label for="subject">Subject:</label></td>
+ <td><input type="text" id="subject" name="subject" maxlength="100" style="margin-right:10px"><input type="submit" value="Post"></td>
+ </tr>
+ {{ end }}
+ <tr>
+ <td><label for="comment">Comment:</label></td>
+ <td><textarea rows="10" cols="50" id="comment" name="comment" maxlength="2000"></textarea></td>
+ </tr>
+ <tr>
+ <td><label for="file">Image</label></td>
+ <td><input type="file" id="file" name="file" {{ if gt $len 1 }} required {{ else }} {{ if eq $len 0 }} required {{ end }} {{ end }} >
+ <br><input type="checkbox" name="sensitive">Mark sensitive</input></td>
+ </tr>
+ <tr>
+ <td><label for="captcha">Captcha:</label></td>
+ <td>
+ <div style="height: 65px; display: inline;">
+ <img src="{{ .Board.Captcha }}">
+ </div>
+ <input type="text" id="captcha" name="captcha" autocomplete="off">
+ </td>
+ </tr>
+ </table>
+
+ <input type="hidden" id="inReplyTo" name="inReplyTo" value="{{ .Board.InReplyTo }}">
+ <input type="hidden" id="sendTo" name="sendTo" value="{{ .Board.To }}">
+ <input type="hidden" id="boardName" name="boardName" value="{{ .Board.Name }}">
+ <input type="hidden" id="captchaCode" name="captchaCode" value="{{ .Board.CaptchaCode }}">
+ <input type="hidden" id="returnTo" name="returnTo" value="{{ .ReturnTo }}"> </form>
+ </div>
+
+ </div>
+ {{ else }}
+ <h1 style="text-align: center;">Archived Post</h1>
+ {{ end }}
+ {{ end }} <!-- end of len eq 0-->
+</div>
+<script>
+ newpostbtn = document.getElementById("newpostbtn");
+ newpost = document.getElementById("newpost");
+
+ if(newpostbtn)
+ newpostbtn.style.display = "block";
+ if(newpost)
+ newpost.style.display = "none";
+</script>