This commit is contained in:
korrinofficialandreal 2023-07-01 23:30:36 -06:00
parent 27c1c5646a
commit 7f89b359c9
106 changed files with 28048 additions and 0 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

60
assets/css/color.css Normal file
View File

@ -0,0 +1,60 @@
#global-menu li a:hover, #global-menu li button:hover {
box-shadow: inset 0 -4px 0 -1px var(--theme);
}
h2.label,
#global-menu #global-my-menu .symbol:before,
.feeling-selector .feeling-button.checked,
.sidebar-setting .symbol:before,
.post-meta .yeah-added + .yeah,
.reply-meta .yeah-added + .yeah,
.post-meta .yeah-added + .yeah:before,
.reply-meta .yeah-added + .yeah:before,
.user-organization,
.user-sidebar .follow-button:before,
.user-sidebar .friend-button:before,
.list .toggle-button .follow-button:before,
.list .toggle-button .follow-done-button:before,
.news-list a.link,
#admin-menu li.selected a {
color: var(--theme);
}
#global-menu li.selected a,
#global-menu li.selected a:before {
color: var(--theme) !important;
}
#nprogress .bar,
h2.reply-label,
#cookie-policy-notice a:hover,
#cookie-policy-notice button:hover,
#cookie-policy-notice .cookie-policy-notice,
#cookie-policy-notice .cookie-policy-notice:hover,
.user-data h4 span,
.index-memo h2:not(.label),
#help .help-content h2,
.tab2 a.selected, .tab3 a.selected,
.textarea-with-menu.active-text .textarea-menu-text,
.textarea-with-menu.active-memo .textarea-menu-memo,
.textarea-with-menu.active-poll .textarea-menu-poll,
.textarea-with-menu .textarea-poll .delete:hover {
background: var(--theme);
}
h2.label {
border-bottom: 3px solid var(--theme);
}
h2.reply-label {
border-top: 1px solid var(--theme);
}
.dialog .window-title {
border-top: 1px solid var(--theme);
border-bottom: 1px solid var(--theme);
background: var(--theme);
}
#reply-content .list .my,
#reply-content .list > li.my:hover {
background: var(--theme-light);
}
@media screen and (max-width: 980px) {
#global-menu li.selected a {
border-bottom: 2px solid var(--theme);
}
}

494
assets/css/dark.css Normal file
View File

@ -0,0 +1,494 @@
/* Credits:
CodyMKW - created the original version for Miiverse
Arian K. - made an updated version for Closedverse
PF2M - colorized everything and later updated it for Indigo
*/
#sidebar-profile-body .nick-name, .news-list .nick-name, .list-content-with-icon-and-text .nick-name a, .user-name a, .file-button-container, .file-button {
color: #00fbff;
}
.post-permalink-button:hover, .favorite-community-link.symbol, .trigger:active, #wrapper, #reply-content button.more-button:hover, .spoiler-button:hover, #sidebar-profile-status, #guide-menu .arrow-button:hover, .community-eyecatch-info:hover, #community-favorite:hover, .big-button:hover, .sidebar-setting a:hover, .multi-timeline-post-list .post.hidden .screenshot-container, .multi-timeline-post-list .post .screenshot-container, .trigger:hover, .textarea-container .community-container, .post-form-album-content, .admin-messages .post.my, #identified-user-banner:hover, .sidebar-setting a.selected, .sidebar-container h4 a:hover, #yeah-content, .sidebar-profile .profile-comment, .post-list .recent-reply-content, .post-list .recent-reply-content .recent-reply-read-more-container:hover, .community-name .owner, .post-list-heading-button, .topic-title-input, .textarea, .yeah-button, .hidden-content-button, .community-description, .file-button-container, .file-button, .repost-error, .messages .post.my, #reply-content .list .my {
background-color: var(--theme-darker, #006466) !important;
}
body, #footer, .favorite-community-link.symbol:hover, .post.hidden:hover, .filtering-label, #guide-menu:hover, #post-diary-window, .spoiler-button, .textarea-line, .dialog .window-body, .digest .post .icon-container, .icon-container .icon, #sidebar-profile-body .icon, .sidebar-container, .post .community-container, #global-menu #global-my-menu, .guest#post-permlink .guest-message p, form.search input[type="text"], form.search input[type="submit"], .warning-content, .community-card-list > li, .post-permalink-button, .repost:hover {
background-color: var(--theme-dark, #006466) !important;
}
body, .community-title, #footer-selector li button, #footer-inner p a, .post-list.empty, .album-list.empty, .list > .no-content, .no-content, .community-game-title, .arrow-button, .post .hidden-content button, .post .deleted-message button, .post .hidden_as_violation button, .post .hidden-content a, .post .deleted-message a, .post .hidden_as_violation a, .post .community-container-heading a, .list-content-with-icon-and-text .text, .list-content-with-icon-and-text .id-name, #sidebar-profile-body .id-name, #post-diary-window p, .sidebar-container h4 a, .post-list .empathized-user-name, .post-list .acted-user-name, .post-list .recommend-user-title, .headline h2, #post-content .post-content-text, #global-menu li a, #global-menu li button, #sidebar-profile-status .number, .community-name, .community-name a, .community-list .title, .sidebar-setting .sidebar-menu-relation:before, #reply-content button.more-button, .sidebar-profile .profile-comment, .guest#post-permlink .guest-message p, form.search input[type="text"], .community-name .owner, .post-list-heading-button, .topic-title-input, .textarea, .yeah-button, .post-permalink-button, .hidden-content-button, .community-description, #admin-menu ul li:not(.selected) a {
color: #ccc !important;
}
input[type=text], input[type=number], input[type=password], input[type=email], input:not([type]), select {
background-color: var(--theme-darker, #180030);
color: #fff;
border: 1px solid #000;
box-shadow: 0 1px 0 rgba(0, 0, 0, 0);
}
.guest .guest-message p {
color: #006466;
background: #006466;
}
.guest-message .arrow-button {
border-top: 1px solid #000 !important;
}
.delete {
color: #006466;
}
.delete:hover {
color: var(--theme-dark, #200040) !important;
}
.spoiler-button, .textarea-line {
border: 1px solid #000;
box-shadow: 0 1px 0 rgba(0, 0, 0, 0);
}
.post-form-album-content {
border: 1px solid #000;
}
.post-form-album-content:before {
border-bottom-color: #000;
}
#sidebar-cover {
border-bottom: 1px solid #000;
}
.post-list-heading-button {
border: 1px solid #000;
border-bottom-color: #000;
}
.filtering-label .button {
border-left: 1px solid #000;
}
.mask {
background: rgba(0, 0, 0, 0.6);
}
.post-list-heading-button:hover {
background: rgb(35, 35, 35);
box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0);
}
.topic-title-input, .textarea {
border: 1px solid #000;
box-shadow: inset 0 2px 6px rgba(0,0,0,0.12), 0 1px 0 #211e1e;
}
.hidden-content-button {
border: 1px solid #000;
border-bottom-color: #000;
box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0);
}
.hidden-content-button:hover {
box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0), 0 2px 4px rgba(0,0,0,0.1);
}
.yeah-button {
border: 1px solid #000;
border-bottom-color: #000;
box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0);
}
.yeah-button:hover {
background: rgb(35, 35, 35);
box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0);
}
.yeah-button:disabled:hover {
background: var(--theme-darker, #100020);
box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0);
}
.setting-form li {
border-bottom: 1px solid #000;
}
form.search input[type="submit"] {
color: #646464;
}
.sidebar-setting .sidebar-menu-relation:before {
color: #000;
}
.community-list-cover {
border-bottom: 1px solid #000;
}
.simple-wrapper.simple-wrapper-content #wrapper {
margin: 0px auto 0;
}
.simple-wrapper.simple-wrapper-content #main-body {
border: 1px solid rgba(21, 20, 20, 0) !important;
}
.warning-content {
border: 1px solid rgb(0, 0, 0);
}
.community-eyecatch-balloon {
color: #CCCCCC;
background: rgb(32, 32, 32);
}
.community-eyecatch-balloon:after {
border-color: transparent rgb(32, 32, 32) rgba(0, 0, 0, 0) transparent;
}
.post-body .multi-timeline-post-list .post-subtype-artwork .hidden-content {
background-color: var(--theme-darker, #100020);
border-top: 1px dashed #000;
}
.community-list .siblings {
color: #969696;
border-left: 1px solid #000;
}
.post-list > .post-list-outline {
border-top: 1px solid rgb(0, 0, 0);
}
#main-body > .no-content {
background: var(--theme-dark, #00fbff) none repeat scroll 0% 0%;
border: 1px solid #000;
box-shadow: 0px 1px 0px #000;
}
.notify {
background: #424242 !important;
}
.tab2 a, .tab3 a {
background-color: var(--theme-dark, #200040);
background: -webkit-gradient(linear, left top, left bottom, from(var(--theme-dark, #200040)), to(var(--theme-dark, #200040)), color-stop(0.5, var(--theme-dark, #200040)));
background: -webkit-linear-gradient(top, var(--theme-dark, #200040) 0%, var(--theme-dark, #200040) 50%, var(--theme-dark, #200040) 100%);
background: -moz-linear-gradient(top, var(--theme-dark, #200040) 0%, var(--theme-dark, #200040) 50%, var(--theme-dark, #200040) 100%);
background: -ms-linear-gradient(top, var(--theme-dark, #200040) 0%, var(--theme-dark, #200040) 50%, var(--theme-dark, #200040) 100%);
background: -o-linear-gradient(top, var(--theme-dark, #200040) 0%, var(--theme-dark, #200040) 50%, var(--theme-dark, #200040) 100%);
background: linear-gradient(top, var(--theme-dark, #200040) 0%, var(--theme-dark, #200040) 50%, var(--theme-dark, #200040) 100%);
color: #CCCCCC;
border: 1px solid #000;
border-bottom-color: #000;
}
.album-dialog .album-close-button {
background: -webkit-linear-gradient(bottom, var(--theme-dark, #200040), var(--theme-dark, #200040));
border: 1px solid #000000;
}
.tab2 a:hover, .tab3 a:hover {
background: var(--theme-darker, #100020);
-webkit-box-shadow: inset 0 1px 0 var(--theme-darker, #100020), 0 2px 4px rgba(22, 22, 22, 0);
-moz-box-shadow: inset 0 1px 0 var(--theme-darker, #100020), 0 2px 4px rgba(22, 22, 22, 0);
-ms-box-shadow: inset 0 1px 0 var(--theme-darker, #100020), 0 2px 4px rgba(22, 22, 22, 0);
-o-box-shadow: inset 0 1px 0 var(--theme-darker, #100020), 0 2px 4px rgba(22, 22, 22, 0);
box-shadow: inset 0 1px 0 var(--theme-darker, #100020), 0 2px 4px rgba(22, 22, 22, 0);
}
.tab2 > a:first-child, .tab3 > a:first-child {
border-left: 1px solid #000;
}
.community-eyecatch-infoicon {
border: 1px solid #000;
}
.open-topic-post-existing-warning {
background-color: rgb(32, 32, 32);
}
.open-topic-post-existing-warning .content {
border: 2px solid #E8316E;
}
.list .toggle-button .button:before {
color: #CCC;
width: 30px;
height: 34px;
border: 1px solid #000;
border-bottom-color: #000;
box-shadow: inset 0 0.5px 0 rgba(0, 0, 0, 0.9);
}
.topic-categories-container {
background: var(--theme-dark, #200040);
-webkit-box-shadow: 0 1px 0 var(--theme-dark, #200040);
-moz-box-shadow: 0 1px 0 var(--theme-dark, #200040);
-ms-box-shadow: 0 1px 0 var(--theme-dark, #200040);
-o-box-shadow: 0 1px 0 var(--theme-dark, #200040);
box-shadow: 0 1px 0 var(--theme-dark, #200040);
}
.button {
border: 1px solid rgb(0, 0, 0);
border-bottom-color: #000;
background: var(--theme-dark, #200040);
color: #ccc;
box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0);
}
.button:hover {
background: var(--theme-darker, #100020);
box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0);
}
.post-list a.another-posts {
background-color: rgba(0, 0, 0, 0.04);
border-top: 1px solid #000;
color: #ccc;
}
.sidebar-setting li {
border-top: 1px solid #000;
}
.community-list .icon-container .icon {
border: 1px solid #000;
}
#sidebar-profile-status li a > span {
display: block;
border-right: 1px solid var(--theme-dark, #200040);
}
#global-menu li#global-menu-mymenu .icon-container img:not(.official-tag) {
border: 1px solid #000;
width: 36px;
height: 36px;
}
#global-menu #global-my-menu {
border: 2px solid #000;
}
#global-menu #global-my-menu:before {
border-color: transparent;
border-bottom-color: #000;
}
#global-menu #global-my-menu:after {
border-color: transparent;
border-bottom-color: var(--theme-dark, #200040);
}
.list-content-with-icon-and-text li {
border-top: 1px solid #000;
}
.filter-button {
color: #ccc;
background-color: rgb(32, 32, 32);
border: 1px solid rgb(0, 0, 0);
}
.filter-button:hover {
background-color: rgba(22,22,22,1);
color: #696666;
}
#community-eyecatch-main > div {
background: var(--theme-dark, #200040);
border: 1px solid rgb(0, 0, 0);
}
#guide-menu {
background: var(--theme-dark, #200040);
border: 1px solid rgb(0, 0, 0);
}
#guide-menu .arrow-button {
border-top: 1px solid #000;
}
#community-favorite {
background: var(--theme-dark, #200040);
border: 1px solid rgb(0, 0, 0);
}
#identified-user-banner {
background: var(--theme-dark, #200040);
border: 1px solid rgb(0, 0, 0);
}
.digest .post .body {
background: var(--theme-dark, #200040);
border: 1px solid rgb(0, 0, 0);
}
.digest .post .body:after {
border-color: transparent var(--theme-dark, #200040) transparent transparent;
}
.digest .post .body:before {
border-color: transparent #000 transparent transparent;
}
.digest .post .community-container {
border: 1px solid #000;
border-top: 1px solid #000;
}
.accepting > span {
background-color: rgba(255, 247, 179, 0);
color: #e33ea2;
}
.accepting > span:before {
color: #f90047;
}
.not-accepting > span {
background-color: rgba(255, 247, 179, 0);
color: #969696;
}
.not-accepting > span:before {
color: #FF0000;
}
.guest#post-permlink .guest-message .arrow-button {
border-top: 1px solid #000;
}
.community-card-list > li {
border: 1px solid rgba(0, 0, 0, 0.1);
border-color: #000;
}
#sub-body {
background: var(--theme-dark, #006466);
border-bottom: 2px solid #000;
box-shadow: 0 0 15px #000;
}
.content-loading-window.activity-feed img {
visibility: hidden;
width: 22px;
height: 22px;
}
.post-form-album-content:after {
border-bottom-color: #1a1a1a;
}
.pager-button {
background: #1A1A1A;
}
.pager-button .back-button {
border-right: 1px solid #000;
}
.pager-button .next-button {
border-left: 1px solid #000;
}
.pager-button .button {
color: #969696;
background-color: #313131;
}
.guest #about {
background: var(--theme-dark, #006466);
border-bottom: 2px solid #000;
}
.guest .guest-terms-link:hover {
background-color: #006466;
}
.tutorial-window {
background-color: rgb(32, 32, 32);
}
.post-list .recent-reply-content {
border: 1px solid #000 !important;
}
.post-list .recent-reply-content .recent-reply-read-more-container {
border-bottom: 1px solid #000 !important;
}
h2.label-diary_post, h2.label-diary {
border-bottom: 3px solid #04c9db;
color: #00b7d8;
}
h2.label-artwork_post, h2.label-artwork {
border-bottom: 3px solid #fcc735;
color: #ffae00;
}
h2.label-topic_post, h2.label-topic {
border-bottom: 3px solid #e8316e;
color: #e8316e;
}
.post-list-outline {
background: var(--theme-dark, #200040);
border: 1px solid rgb(0, 0, 0);
-webkit-box-shadow: 0 1px 0px rgba(0, 0, 0, 0.2);
-moz-box-shadow: 0 1px 0px rgba(0, 0, 0, 0.2);
-ms-box-shadow: 0 1px 0px rgba(0, 0, 0, 0.2);
-o-box-shadow: 0 1px 0px rgba(0, 0, 0, 0.2);
box-shadow: 0 1px 0px rgba(0, 0, 0, 0.2);
}
.post .community-container {
border-bottom: 1px solid #000;
}
.list > li, .list > div, .list > a, .list > span {
border-top: 1px solid #000;
}
/*.list > li span {
padding: 5px!important;
}*/
#sidebar-community .sidebar-setting a {
border-top: 1px solid #000;
}
.sidebar-container {
border: 1px solid rgb(0, 0, 0);
-webkit-box-shadow: 0 1px 0px rgba(0, 0, 0, 0.2);
-moz-box-shadow: 0 1px 0px rgba(0, 0, 0, 0.2);
-ms-box-shadow: 0 1px 0px rgba(0, 0, 0, 0.2);
-o-box-shadow: 0 1px 0px rgba(0, 0, 0, 0.2);
box-shadow: 0 1px 0px rgba(0, 0, 0, 0.2);
}
.big-button {
background: #fff;
border: 1px solid rgb(0, 0, 0);
-webkit-box-shadow: 0 1px 0px rgba(0, 0, 0, 0.2);
-moz-box-shadow: 0 1px 0px rgba(0, 0, 0, 0.2);
-ms-box-shadow: 0 1px 0px rgba(0, 0, 0, 0.2);
-o-box-shadow: 0 1px 0px rgba(0, 0, 0, 0.2);
box-shadow: 0 1px 0px rgba(0, 0, 0, 0.2);
background-color: var(--theme-dark, #200040);
color: #ccc;
}
form.search {
background: #fff;
border: 2px solid #000;
}
.sidebar-setting a {
color: #ccc;
border-top: 1px solid #000;
}
.user-data .note {
border-bottom: 2px dashed var(--theme-darker, #100020);
color: #ccc;
}
#sidebar-profile-body .icon {
border: 1px solid #000;
}
.icon-container .icon {
border: 1px solid #000;
}
.digest .post .icon-container {
border: 1px solid rgb(0, 0, 0);
}
#yeah-content .post-permalink-feeling-icon .user-icon {
border: 1px solid #000;
background: transparent;
}
#yeah-content.none + .buttons-content {
border-top: 1px solid #222;
}
#yeah-content {
border: 1px solid #000;
}
#reply-content button.more-button {
border: #000;
}
#reply-content .list .my.trigger:hover,
#reply-content .list .my.trigger:hover .yeah-button,
.list-content-with-icon-and-text .trigger.selected {
background-color: #181818 !important;
}
#reply-content .info-reply-list + button.more-button, #reply-content .more-button + .more-button {
border-top: 1px solid #000;
}
.dialog .window-body {
border: 1px solid #050207;
}
#post-form + #community-post-list .list > div:first-child {
border-top: 1px solid #000;
}
.user-community .icon-container .user-icon {
background: rgba(255, 255, 255, 0);
border: 1px solid rgba(221, 221, 221, 0);
}
#wrapper, #image-header-content {
background: var(--theme, #00fbff) url('data:image/png;iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAEpElEQVR4nO2dW3LiQBAEE8kmOPae2w7j/ZjFa0CAHi1N16jqABMZlf2pKB1Op9MfgnI8Hun7Puq5q5zPZz4+Pvj+/g55z6yFs4t6zIWWqLBeOEMOwIWWqLD+5lx8AC60RIX1lnPRAbjQEhXWIc7ZB+BCS1RYH3HOOgAXWqLC+oxz8gG40BIV1leckw7AhZaosI7hHH0ALrREhXUs56gDcKElKqxTOF8egAstUWGdyvn0AFxoiQrrHM6HB+BCS1RY53IOHoALLVFhXcJ5dwAutESFdSnn1QG40BIV1gjOnwNwoSUqrFGcHbjQS1RYIzk7F1qiwhrN2e29UNBhXYPzLeSlm3x9ffH5+Zm+UNi3/L7v4z4KvcTydeTDwk/CbmP5WvIh8AAsX08+BB2A5WvKh4ADOJ/Plh+cLTkXHYBKoaDDujXn7ANQKRR0WGtwzjoAlUJBh7UW5+QDUCkUdFhrck46AJVCQYe1NufoA6gNOiUqrBk4Rx1ABtCxUWHNwvnyALKAjokKaybOpweQCfRVVFizcT48gGygz6LCmpFz8AAygj6KCmtWzrsDyAo6FBXWzJxXB5AZ9DYqrNk5fw4gO+jvqLAqcHagAXqJCqsKZ6cCCjqlqnACdCqgKqWqcEL5kqtTAFUpVYUTinxPxQZGhRP+y4eAbwItX4cTruWDp2IXR4UT7uWDp2IXRYUThuWDp2JnR4UTHssHT8XOigonPJcPnoqdHBVOeC0fPBU7KSqcME4+eCp2dFQ4Ybx88FTsqKhwwjT54KnYl1HhhOnywVOxT6PCCfPkg6diH0aFE+bLB0/FDkaFE5bJB0/F3kWFE5bLB0/FXkWFE2Lkg6dif6LCCXHywVOxgA4nxMo/HA6eilXhhHj5x+MxfikUdEpV4YSV5Hdd/AGolKrCCevJh+CpWJVSVThhXfkQeAAqpapwwvryIegAVEpV4YRt5EPQVKxCqSqcsJ182MlUrAonbCsfdjAVq8IJ28uHxqdiVTihjnxoeCpWhRPqyYdGp2JVOKGufGhwKlaFE+rLh8amYlU4IYd8aGgqVoUT8siHRqZiVTghl3xoYCpWhRPyyQfxqVgVTsgpH4SnYlU4Ia98EJ2KVeGE3PJBcCpWhRPyywexqVgVTtCQD/Cm8t9fy1/l+106y4+NknyJqVjLX09++qlYy19XPiSeirX89eVD0qlYy99GPiScirX87eRDsqlYy99WPiSairX87eVDkqlYy68jHxJMxVp+PflQeSrW8uvKh4pTsZZfXz5Umoq1/BzyocJUrOXnkQ8bT8Vafi75sOFUrOXnkw8bTcVafk75sMFUrOXnlQ8rT8Vafm75sOJUrOXnl7/aVKzla8h/f3/f71IoWH7f97yFvPgvlq8hf5WpWMvXkw87WwoFyw+firV8Xfmwk6VQsPzwqVjL15cPjS+FguWHT8VafjvyodGlULD88KlYy29PPjS2FAr1C52SDKzNLIVCjkLHJgtrE0uhkKfQMcnEKr8UCrkKfZVsrNJLoZCv0GfJyCq7FAo5C32UrKySS6GQt9ChZGaVWwqF3IXeJjur1FIo5C/0dxRYO8vfr3yAv3rECG2I4F0DAAAAAElFTkSuQmCC') !important;
}
#yeah-content:before {
background: url(/assets/img/rks.png) no-repeat 0 0 !important;
}
.community-switcher .community-switcher-tab {
background-color: var(--theme-dark, #200040);
background: -webkit-gradient(linear, left top, left bottom, from(var(--theme-dark, #200040)), to(var(--theme-dark, #200040)), color-stop(0.5, var(--theme-dark, #200040)));
background: -webkit-linear-gradient(top, var(--theme-dark, #200040) 0%, var(--theme-dark, #200040) 50%, var(--theme-dark, #200040) 100%);
background: -moz-linear-gradient(top, var(--theme-dark, #200040) 0%, var(--theme-dark, #200040) 50%, var(--theme-dark, #200040) 100%);
background: -ms-linear-gradient(top, var(--theme-dark, #200040) 0%, var(--theme-dark, #200040) 50%, var(--theme-dark, #200040) 100%);
background: -o-linear-gradient(top, var(--theme-dark, #200040) 0%, var(--theme-dark, #200040) 50%, var(--theme-dark, #200040) 100%);
background: linear-gradient(top, var(--theme-dark, #200040) 0%, var(--theme-dark, #200040) 50%, var(--theme-dark, #200040) 100%);
border: 1px solid black;
color: #fff;
}
.community-switcher .community-switcher-tab:first-of-type {
border-left: 1px solid black;
}
code,
pre {
border: 1px solid #404050 !important;
color: #CCC !important;
background-color: var(--theme-darker, #100020) !important;
}
@media screen and (max-width: 980px) {
#global-menu #global-menu-list > ul > li > a:hover {
background-color: #333 !important;
}
#global-menu #global-menu-list > ul > li#global-menu-my-menu button:hover {
background-color: #333 !important;
}
}
@media screen and (max-width: 640px) {
.list .toggle-button .button:before {
height: 28px !important;
}
}
#my-menu-logout input {
color: #ccc !important;
}
.warning-content {
color: #ccc;
}
.post-migration-label {
background-color: #333;
}
.character-count {
color: #fff;
}

11091
assets/css/offdevice.css Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
assets/img/anime.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

BIN
assets/img/anonymous.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

BIN
assets/img/art/ketamine.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
assets/img/ash.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 MiB

BIN
assets/img/background.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 919 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 B

BIN
assets/img/bg-gray-dot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 B

BIN
assets/img/bg-gray.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 413 B

BIN
assets/img/bg-select.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 576 B

BIN
assets/img/bocchi.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 MiB

BIN
assets/img/border.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

BIN
assets/img/button-bg.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 329 B

BIN
assets/img/dancingjesus.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 KiB

BIN
assets/img/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

BIN
assets/img/form-icons.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

BIN
assets/img/go!.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 410 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 581 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 B

BIN
assets/img/loading.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
assets/img/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 B

BIN
assets/img/menu-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
assets/img/monkeycar.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
assets/img/nova-favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

BIN
assets/img/premiumad.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
assets/img/restricted.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

BIN
assets/img/rks.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 B

BIN
assets/img/scoobydio.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

BIN
assets/img/sign-in.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

BIN
assets/img/weedchan.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
assets/img/wojak.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

BIN
assets/img/yaoi1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

3863
assets/js/indigo.js Normal file

File diff suppressed because one or more lines are too long

31
assets/js/jslibs.js Normal file

File diff suppressed because one or more lines are too long

187
assets/js/upload.js Normal file
View File

@ -0,0 +1,187 @@
function checkForm() {
if($("textarea[name=body]").length && ($("textarea[name=body]").val().length > 0 || $("input[name=image]").val().length > 0)) {
Olv.Form.toggleDisabled($("input.post-button"), false);
Olv.Form.toggleDisabled($("input.reply-button"), false);
} else {
Olv.Form.toggleDisabled($("input.post-button"), true);
Olv.Form.toggleDisabled($("input.reply-button"), true);
}
}
function showError(error) {
console.log(error);
$("input[name=image]").val("");
if(!$(".file-upload-button").hasClass("for-avatar")) {
$(".preview-container").css("display", "none");
checkForm();
}
$(".file-button").removeAttr("disabled");
$(".file-button").val(null);
$(".file-upload-button").text("Upload");
Olv.showMessage("Attachment upload failed", "There was an error trying to upload your attachment.\nThe response received from the server was this:\n" + error.responseText);
}
function postFile(file, fileType, isDrawing, inputName) {
if(!fileType.startsWith("image/") && !fileType.startsWith("audio/") && !fileType.startsWith("video/")) return $("input[name=image]").val(""), Olv.showMessage("Error", "Invalid file type."), $(".file-button").removeAttr("disabled"), $(".file-button").val(null), void $(".file-upload-button").text("Upload");
var formData = new FormData();
formData.append(inputName, file);
var csrfTokenData = Olv.Form.csrftoken({});
formData.append('csrfmiddlewaretoken', csrfTokenData.csrfmiddlewaretoken);
$.ajax({
url: '/upload',
type: 'POST',
data: formData,
cache: false,
contentType: false,
processData: false,
success: function(data) {
if(isDrawing) {
$("input[name=painting]").val(data);
} else {
$("input[name=" + inputName + "]").val(data);
}
if(fileType.startsWith("audio/")) {
$(".preview-audio").attr("src", URL.createObjectURL(file));
$(".preview-image").addClass("none");
$(".preview-audio").removeClass("none");
$(".preview-video").addClass("none");
$("input[name=attachment_type]").val("1");
} else if(fileType.startsWith("video/")) {
$(".preview-video").attr("src", URL.createObjectURL(file));
$(".preview-image").addClass("none");
$(".preview-audio").addClass("none");
$(".preview-video").removeClass("none");
$("input[name=attachment_type]").val("2");
} else if($(".file-upload-button").hasClass("for-avatar")) {
$(".preview-image").attr("src", URL.createObjectURL(file));
$(".preview-image").removeClass("none");
} else {
$("input[name=" + inputName + "]").siblings(".screenshot-container").children(".preview-image").attr("src", URL.createObjectURL(file));
$("input[name=" + inputName + "]").siblings(".screenshot-container").children(".preview-image").removeClass("none");
$(".preview-audio").addClass("none");
$(".preview-video").addClass("none");
$("input[name=attachment_type]").val("0");
}
if(!isDrawing && !$(".file-upload-button").hasClass("for-avatar")) {
$(".preview-container").attr("style", "");
checkForm();
} else if(!isDrawing) {
$("input[name=avatar][value=0]").prop("checked", true).change();
} else {
$("#drawing").remove();
$(".textarea-memo").append("<img id=\"drawing\" src=\"" + URL.createObjectURL(file) + "\" style=\"background:white;\"></img>");
Olv.Form.toggleDisabled($("input.post-button"), false);
Olv.Form.toggleDisabled($(".memo-finish-btn"), false);
}
if(!isDrawing) {
Olv.Form.toggleDisabled($(".file-button"), false);
$(".file-button").val(null);
$(".file-upload-button").text("Upload");
}
},
error: function(error) {
showError(error);
}
});
}
function handleChange(event) {
console.log(event);
var inputName = "image";
if($(this).attr("id") !== undefined) inputName = $(this).attr("id");
if(this.files.length) {
Olv.Form.toggleDisabled($("input.post-button"), true);
$("input[name=" + inputName + "]").siblings(".file-button").attr("disabled", "disabled");
$("input[name=" + inputName + "]").siblings(".file-upload-button").text("Uploading...");
var fileType = this.files[0].type;
var file = this.files[0];
if(($(".file-upload-button").hasClass("for-avatar") || inputName === "icon") && fileType !== "image/gif") {
var img = new Image();
img.src = URL.createObjectURL(file);
img.onload = function() {
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
ctx.imageSmoothingQuality = "high";
var size = 128, factor, startX, startY, resizeWidth, resizeHeight;
canvas.width = size;
canvas.height = size;
if(img.width > img.height) {
factor = img.width / img.height;
startX = (img.width - img.height) / 2;
startY = 0;
resizeWidth = size * factor;
resizeHeight = size;
} else if(img.height > img.width) {
factor = img.height / img.width;
startX = 0;
startY = (img.height - img.width) / 2;
resizeWidth = size;
resizeHeight = size * factor;
} else {
factor = 1;
startX = 0;
startY = 0;
resizeWidth = size;
resizeHeight = size;
}
ctx.drawImage(img, startX, startY, img.width, img.height, 0, 0, resizeWidth, resizeHeight);
canvas.toBlob(function(blob) {
postFile(blob, fileType, false, inputName);
});
}
} else {
postFile(file, fileType, false, inputName);
}
} else {
$("input[name=image]").val("");
if(!$(".file-upload-button").hasClass("for-avatar")) {
$(".preview-container").css("display", "none");
checkForm();
}
}
}
function handleDropPaste(event) {
if($(this).siblings(".file-button").attr("disabled")) {
return;
}
var files;
switch(event.type) {
case "drop":
files = event.originalEvent.dataTransfer.files;
break;
case "paste":
if(event.originalEvent.clipboardData.files.length == 0) {
return;
}
files = event.originalEvent.clipboardData.files;
break;
default:
return;
}
event.stopPropagation();
event.preventDefault();
if(files[0].type.startsWith("image/") || files[0].type.startsWith("audio/") || files[0].type.startsWith("video/")) {
$(".file-button")[0].files = files;
$(".file-button").trigger("change");
} else {
Olv.showMessage("Attachment upload failed", "You can only upload images, audio or videos.");
}
}
function handleDrag(event) {
event.stopPropagation();
event.preventDefault();
event.originalEvent.dataTransfer.dropEffect = "copy";
}
function init() {
$(".file-button").off().on("change", handleChange);
$(document).off("dragover dragenter").on("dragover dragenter", handleDrag);
$(document).off("drop paste").on("drop paste", handleDropPaste);
}
$(document).off("ready", init).off("pjax:end", init).on("ready", init).on("pjax:end", init);
init();

214
assets/js/websocket.js Normal file
View File

@ -0,0 +1,214 @@
var waitingTime = 1000;
var conn;
function initWebsockets() {
conn = new WebSocket('ws' + (window.location.protocol === 'https' ? 's' : '') + '://' + window.location.host + '/ws');
conn.onopen = function() {
console.log("Connection established!");
var message = '{"type":"onPage","content":"'+ window.location.pathname +'"}';
conn.send(message);
};
conn.onmessage = function(e) {
var message = JSON.parse(e.data);
switch(message.type) {
case "comment":
$('.reply-list').append(message.content);
$('.post').last().hide().fadeIn(400);
Olv.Entry.incrementReplyCount(1);
break;
case "commentPreview":
$('#' + message.id).find('.recent-reply-content').remove();
$('#' + message.id).find('.post-meta').after(message.content);
var commentCount = parseInt($('#' + message.id).find('.reply-count').text());
$('#' + message.id).find('.reply-count').text(commentCount + 1);
if(commentCount > 1) {
$('#' + message.id).find('.recent-reply-content').prepend('<div class="recent-reply-read-more-container" tabindex="0">View all comments (' + (commentCount + 1) + ')</div>');
}
break;
case "notif":
$('#global-menu-news').find('.badge').text(message.content);
if(message.content > 0) {
$('#global-menu-news .badge').show();
} else {
$('#global-menu-news .badge').hide();
}
getNewFaviconBadge();
break;
case "messageNotif":
$('#global-menu-message').find('.badge').text(message.content);
if(message.content > 0) {
$('#global-menu-message .badge').show();
} else {
$('#global-menu-message .badge').hide();
}
getNewFaviconBadge();
break;
case "postYeah":
if(window.location.pathname.substr(1, 5) == "posts") {
var yeahCount = parseInt($('#the-post').find('.yeah-count').text());
$('#the-post').find('.yeah-count').text(yeahCount + 1);
$('#yeah-content').removeClass('none').prepend(message.content);
} else {
var yeahCount = parseInt($('#' + message.id).find('.yeah-count').text());
$('#' + message.id).find('.yeah-count').text(yeahCount + 1);
}
break;
case "postUnyeah":
if(window.location.pathname.substr(1, 5) == "posts") {
var yeahCount = parseInt($('#the-post').find('.yeah-count').text());
$('#the-post').find('.yeah-count').text(yeahCount - 1);
$('#yeah-content').find('#' + message.content).remove();
if(yeahCount - 1 == 0) {
$('#yeah-content').addClass('none');
}
} else {
var yeahCount = parseInt($('#' + message.id).find('.yeah-count').text());
$('#' + message.id).find('.yeah-count').text(yeahCount - 1);
}
break;
case "postEdit":
if($("#post-content").length) {
$('#post-content').find('.post-content-text').html(message.content);
} else {
$('#' + message.id).find('.post-content-text').html(message.content);
}
break;
case "pollVote":
var pollOption = $(".poll-option[option-id=" + message.id + "]");
pollOption.attr("votes", parseInt(pollOption.attr("votes")) + 1);
recalculateVotes(pollOption.siblings(".poll-option").addBack());
break;
case "pollUnvote":
var pollOption = $(".poll-option[option-id=" + message.id + "]");
pollOption.attr("votes", pollOption.attr("votes") - 1);
recalculateVotes(pollOption.siblings(".poll-option").addBack());
break;
case "pollChange":
var pollOption = $(".poll-option[option-id=" + message.id + "]");
pollOption.attr("votes", parseInt(pollOption.attr("votes")) + 1);
var oldPollOption = $(".poll-option[option-id=" + message.content + "]");
oldPollOption.attr("votes", oldPollOption.attr("votes") - 1);
recalculateVotes(pollOption.siblings(".poll-option").addBack());
break;
case "commentYeah":
if(window.location.pathname.substr(1, 8) == "comments") {
var yeahCount = parseInt($('#the-post').find('.yeah-count').text());
$('#the-post').find('.yeah-count').text(yeahCount + 1);
$('#yeah-content').removeClass('none').prepend(message.content);
} else {
var yeahCount = parseInt($('#' + message.id).find('.yeah-count').text());
$('#' + message.id).find('.yeah-count').text(yeahCount + 1);
}
break;
case "commentUnyeah":
if(window.location.pathname.substr(1, 8) == "comments") {
var yeahCount = parseInt($('#the-post').find('.yeah-count').text());
$('#the-post').find('.yeah-count').text(yeahCount - 1);
$('#yeah-content').find('#' + message.content).remove();
if(yeahCount - 1 == 0) {
$('#yeah-content').addClass('none');
}
} else {
var yeahCount = parseInt($('#' + message.id).find('.yeah-count').text());
$('#' + message.id).find('.yeah-count').text(yeahCount - 1);
}
break;
case "commentEdit":
$('#' + message.id).find('.reply-content-text').html(message.content);
break;
case "follow":
var followCount = parseInt($('.test-follower-count').text());
$('.test-follower-count').text(followCount + 1);
break;
case "unfollow":
var followCount = parseInt($('.test-follower-count').text());
$('.test-follower-count').text(followCount - 1);
break;
case "online":
$('.icon-container[username="' + message.content + '"].offline').removeClass('offline').addClass('online');
break;
case "offline":
$('.icon-container[username="' + message.content + '"].online').removeClass('online').addClass('offline');
break;
case "block":
if(window.location.pathname.startsWith("/users/" + message.content)) {
Olv.Form.toggleDisabled($('.post:has(.icon-container[username="' + message.content + '"])').find(".yeah-button"), true);
} else {
$('.post:not(#post-content):has(.icon-container[username="' + message.content + '"])').remove();
}
Olv.Form.toggleDisabled($('#post-content:has(.icon-container[username="' + message.content + '"])').find(".yeah-button"), true);
break;
case "unblock":
Olv.Form.toggleDisabled($('.post:has(.icon-container[username="' + message.content + '"])').find(".yeah-button"), false);
break;
case "delete":
$('.post#' + message.id).remove();
break;
case "refresh":
location.reload();
break;
case "post":
$('.post-list').prepend(message.content);
$('.post').first().hide().fadeIn(400);
$(".no-post-content").addClass("none");
if(window.location.pathname.startsWith("/communities/") && !$(message.content).hasClass("pinned")) {
$(".pinned:not(.repost)").prependTo('.post-list');
}
break;
case "message":
$('.messages').prepend(message.content);
$('.post').first().hide().fadeIn(400);
$(".no-content").remove();
break;
case "messagePreview":
message.content = JSON.parse(message.content);
if(message.content.URLType == 1) {
$('.list-content-with-icon-and-text li[data-href="/conversations/' + message.content.ByUsername + '"]').remove();
} else {
$('.list-content-with-icon-and-text li:has(.icon-container[username="' + message.content.ByUsername + '"])').remove();
}
$(".list-content-with-icon-and-text").prepend(`<li class="trigger notify" data-href="/${message.content.URLType ? "conversations" : "messages"}/${message.content.ByUsername}"><a href="/${message.content.URLType ? "conversations" : "users"}/${message.content.ByUsername}" username="${message.content.URLType ? "" : message.content.ByUsername}" class="icon-container ${!message.content.ByHideOnline ? message.content.ByOnline ? 'online' : 'offline' : ''} ${message.content.ByRoleImage ? 'official-user"><img src="' + message.content.ByRoleImage + '" class=official-tag>' : '">'}<img src="${message.content.ByAvatar}" class=icon></a><div class=body><p class=title><span class=nick-name><a href="/${message.content.URLType ? "conversations" : "users"}/${message.content.ByUsername}">${message.content.URL}</a></span> <span class=id-name>${message.content.URLType ? "" : message.content.ByUsername}</span></p><span class=timestamp>${message.content.Date}</span><p class="text other${message.content.BodyText ? '">' + message.content.BodyText : "text-memo\">(attachment)"}</p></div></li>`);
$("#global-menu-message .badge").text(parseInt($("#global-menu-message .badge").text()) + 1).show();
getNewFaviconBadge();
break;
default:
console.log(message);
break;
}
}
conn.onclose = function() {
if(websocketsEnabled) {
$('.icon-container[username="' + $("body").attr("sess-usern") + '"].online').removeClass('online').addClass('offline');
console.log("Disconnected, reconnecting in " + waitingTime / 1000 + " seconds");
setTimeout(function() {
waitingTime *= 2;
console.log("Reconnecting...");
initWebsockets();
}, waitingTime);
}
}
$(document).off("pjax:end", onPJAXEnd).on("pjax:end", onPJAXEnd);
}
function onPJAXEnd() {
var message = '{"type":"onPage","content":"'+ window.location.pathname +'"}';
conn.send(message);
}
function toggleWebsockets() {
if(websocketsEnabled) {
conn.close(1000);
websocketsEnabled = false;
$('.icon-container[username="' + $("body").attr("sess-usern") + '"].online').removeClass('online').addClass('offline');
$(document).off("pjax:end", onPJAXEnd);
} else {
websocketsEnabled = true;
initWebsockets();
}
}
var websocketsEnabled = true;
initWebsockets();

6076
handlers.go Normal file

File diff suppressed because it is too large Load Diff

BIN
img/module_table_bottom.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 751 B

BIN
img/module_table_top.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 B

11
infucks.php Normal file
View File

@ -0,0 +1,11 @@
<?php
if (!empty($_SERVER['HTTPS']) && ('on' == $_SERVER['HTTPS'])) {
$uri = 'https://';
} else {
$uri = 'http://';
}
$uri .= $_SERVER['HTTP_HOST'];
header('Location: '.$uri.'/dashboard/');
exit;
?>
Something is wrong with the XAMPP installation :-(

383
main.go Normal file
View File

@ -0,0 +1,383 @@
////////////////////////
// //
// Indigo //
// The Miiverse clone //
// that will end all //
// other Miiverse //
// clones, for real //
// this time. //
// //
// Lead Devs: PF2M, //
// Seth/EnergeticBark //
// //
// Developers: Ben, //
// triangles.py, jod, //
// & Chance/SRGNation //
// //
// Artwork: Spicy & //
// Inverse & Gnarly //
// //
// Marketing: Pip //
// //
// Testing: Mippy ♥ //
// //
// https://github.com //
// /PF2M/Indigo //
// //
////////////////////////
package main
// Import dependencies.
import (
// Internals
"database/sql"
"encoding/json"
"html/template"
"log"
"net"
"net/http"
"os"
"path/filepath"
// "user" is already defined in types
osUser "os/user"
"strconv"
"regexp"
// Externals
"github.com/NYTimes/gziphandler"
_ "github.com/go-sql-driver/mysql"
"github.com/gorilla/csrf"
"github.com/gorilla/mux"
"github.com/gorilla/websocket"
"github.com/oschwald/geoip2-golang"
"github.com/russross/blackfriday/v2"
)
// Initialize some variables.
var db *sql.DB
var err error
var clients = make(map[*websocket.Conn]*wsSession)
var settings config
var admin adminConfig
var youtube *regexp.Regexp
var spotify *regexp.Regexp
var soundcloud *regexp.Regexp
var symbols *regexp.Regexp
var emotes *regexp.Regexp
var renderer *blackfriday.HTMLRenderer
var geoip *geoip2.Reader
var isGeoIPEnabled bool
// Configure the upgrader.
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
// Todo: Add to this if necessary
return true
},
EnableCompression: true,
}
// Define the templates.
var templates *template.Template
// Redirect HTTP requests to HTTPS if properly configured.
func redirect(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "https://"+r.Host+r.URL.Path, http.StatusTemporaryRedirect)
}
// Now let's start the main function!
func main() {
// Fetch the site's settings from JSON files.
settings = getSettings()
adminJSON, err := os.ReadFile("admin.json")
if err != nil {
log.Fatal(err)
}
err = json.Unmarshal(adminJSON, &admin)
if err != nil {
log.Fatal(err)
}
// Connect to the database.
db, err = sql.Open("mysql", settings.DB.Username+":"+settings.DB.Password+"@tcp("+settings.DB.Host+")/"+settings.DB.Name+"?parseTime=true&loc=US%2FEastern&charset=utf8mb4,utf8")
if err != nil {
log.Printf("[err]: unable to connect to the database...\n")
log.Printf(" %v\n", err)
os.Exit(1)
}
// Ping the database to make sure we connected properly.
err = db.Ping()
if err != nil {
log.Printf("[err]: unable to ping the database...\n")
log.Printf(" %v\n", err)
os.Exit(1)
}
_, err = db.Exec("SET CHARACTER SET utf8mb4")
if err != nil {
log.Printf("[err]: unable to set the character set...\n")
log.Printf(" %v\n", err)
os.Exit(1)
}
_, err = db.Exec("SET collation_connection = utf8mb4_bin")
if err != nil {
log.Printf("[err]: unable to set the connection collation...\n")
log.Printf(" %v\n", err)
os.Exit(1)
}
// Initialize some regex.
youtube, _ = regexp.Compile("(?:youtube\\.com/\\S*(?:(?:/e(?:mbed))?/|watch/?\\?(?:\\S*?&?v=))|youtu\\.be/)([a-zA-Z0-9_-]{6,11})")
spotify, _ = regexp.Compile("(?:embed\\.|open\\.)(?:spotify\\.com/)(?:track/|\\?uri=spotify:track:)((\\w|-){22})")
soundcloud, _ = regexp.Compile("(soundcloud\\.com|snd\\.sc)(.*)")
symbols, _ = regexp.Compile("(\\|\\\\|`|\\*|{|}|\\[|\\](|)|\\+|-|!|_|>|\\n|&|:|<)")
emotes, err = regexp.Compile(":([^ :]+):")
if err != nil {
log.Fatal(err)
}
// Initialize Markdown renderer.
renderer = blackfriday.NewHTMLRenderer(blackfriday.HTMLRendererParameters{
Flags: 2 | 4 | 128,
})
// Initialize GeoIP if a database is present.
isGeoIPEnabled = false
if _, err = os.Stat("geoip.mmdb"); err == nil {
geoip, err = geoip2.Open("geoip.mmdb")
if err != nil {
log.Fatal(err)
}
defer geoip.Close()
isGeoIPEnabled = true
}
// Wipe the online statuses of all the users and delete all session keys (necessary after crashes, shutdowns, etc.)
db.QueryRow("UPDATE users SET online = 0").Scan()
db.QueryRow("TRUNCATE TABLE sessions").Scan()
// Close the database connection after this function exits.
defer db.Close()
// initialize the templates by parsing everything from the views directory recursively
var tmplFiles []string
err = filepath.Walk("views", func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// exclude non-html files
if !info.IsDir() && filepath.Ext(path) == ".html" {
// feel free to instead make this directly build the template
tmplFiles = append(tmplFiles, path)
}
return nil
})
if err != nil {
log.Fatal("could not add or find templates (they are stored in views, is this accessible?): ", err)
}
templates = template.Must(template.ParseFiles(tmplFiles...))
// make the directory for the local image provider if it doesn't exist
if settings.ImageHost.Provider == "local" {
// check if the error is specifically os.IsNotExist
if _, err := os.Stat(settings.ImageHost.ImageEndpoint); os.IsNotExist(err) {
// should make it in this working directory
err = os.MkdirAll(settings.ImageHost.ImageEndpoint, 0755)
if err != nil {
log.Println("could not make \""+settings.ImageHost.ImageEndpoint+"\" directory for local image host:", err)
}
}
}
// Set up CSRF.
CSRF := csrf.Protect([]byte(settings.CSRFSecret), csrf.FieldName("csrfmiddlewaretoken"), csrf.Path("/"), csrf.Secure(settings.SSL.Enabled))
// Initialize routes.
r := mux.NewRouter()
// functions that don't useLogin or requireLogin,
// they don't necessarily not access the user
// but just do it independently, not utilizing the CurrentUser
// Index route.
r.HandleFunc("/", useLogin(index)).Methods("GET")
// Auth routes.
r.HandleFunc("/signup", signup).Methods("GET", "POST")
r.HandleFunc("/login", login).Methods("GET", "POST")
r.HandleFunc("/logout", logout).Methods("POST")
r.HandleFunc("/reset", useLogin(resetPassword)).Methods("GET", "POST").Queries("token", "{token}")
r.HandleFunc("/reset", useLogin(showResetPassword)).Methods("GET", "POST")
// User routes.
r.HandleFunc("/users", requireLogin(showUserSearch)).Methods("GET").Queries("query", "{username}")
r.HandleFunc("/users/{username}", useLogin(showUser)).Methods("GET")
r.HandleFunc("/users/{username}/posts", useLogin(showUserPosts)).Methods("GET")
r.HandleFunc("/users/{username}/comments", useLogin(showUserComments)).Methods("GET")
r.HandleFunc("/users/{username}/yeahs", useLogin(showUserYeahs)).Methods("GET")
r.HandleFunc("/users/{username}/friends", useLogin(showFriends)).Methods("GET")
r.HandleFunc("/users/{username}/following", useLogin(showFollowing)).Methods("GET")
r.HandleFunc("/users/{username}/followers", useLogin(showFollowers)).Methods("GET")
r.HandleFunc("/users/{username}/favorites", useLogin(showFavorites)).Methods("GET")
r.HandleFunc("/users/{username}/friend_new", requireLogin(newFriendRequest)).Methods("POST")
r.HandleFunc("/users/{username}/friend_accept", requireLogin(acceptFriendRequest)).Methods("POST")
r.HandleFunc("/users/{username}/friend_reject", requireLogin(rejectFriendRequest)).Methods("POST")
r.HandleFunc("/users/{username}/friend_cancel", requireLogin(cancelFriendRequest)).Methods("POST")
r.HandleFunc("/users/{username}/friend_delete", requireLogin(deleteFriend)).Methods("POST")
r.HandleFunc("/users/{username}/follow", requireLogin(createFollow)).Methods("POST")
r.HandleFunc("/users/{username}/unfollow", requireLogin(deleteFollow)).Methods("POST")
r.HandleFunc("/users/{username}/violators", requireLogin(reportUser)).Methods("POST")
r.HandleFunc("/users/{username}/block", requireLogin(blockUser)).Methods("POST")
r.HandleFunc("/users/{username}/unblock", requireLogin(unblockUser)).Methods("POST")
// Post routes.
r.HandleFunc("/posts/{id:[0-9]+}", useLogin(showPost)).Methods("GET")
r.HandleFunc("/posts/{id:[0-9]+}/yeah", requireLogin(createPostYeah)).Methods("POST")
r.HandleFunc("/posts/{id:[0-9]+}/yeahu", requireLogin(deletePostYeah)).Methods("POST")
r.HandleFunc("/posts/{id:[0-9]+}/comments", useLogin(showAllComments)).Methods("GET")
r.HandleFunc("/posts/{id:[0-9]+}/comments", requireLogin(createComment)).Methods("POST")
r.HandleFunc("/posts/{id:[0-9]+}/favorite", requireLogin(favoritePost)).Methods("POST")
r.HandleFunc("/posts/{id:[0-9]+}/unfavorite", requireLogin(unfavoritePost)).Methods("POST")
r.HandleFunc("/posts/{id:[0-9]+}/violations", requireLogin(reportPost)).Methods("POST")
r.HandleFunc("/posts/{id:[0-9]+}/vote", requireLogin(voteOnPoll)).Methods("POST")
r.HandleFunc("/posts/{id:[0-9]+}/edit", requireLogin(editPost)).Methods("POST")
r.HandleFunc("/posts/{id:[0-9]+}/delete", requireLogin(deletePost)).Methods("POST")
// Comment routes.
r.HandleFunc("/comments/{id:[0-9]+}", useLogin(showComment)).Methods("GET")
r.HandleFunc("/comments/{id:[0-9]+}/yeah", requireLogin(createCommentYeah)).Methods("POST")
r.HandleFunc("/comments/{id:[0-9]+}/yeahu", requireLogin(deleteCommentYeah)).Methods("POST")
r.HandleFunc("/comments/{id:[0-9]+}/violations", requireLogin(reportComment)).Methods("POST")
r.HandleFunc("/comments/{id:[0-9]+}/edit", requireLogin(editComment)).Methods("POST")
r.HandleFunc("/comments/{id:[0-9]+}/delete", requireLogin(deleteComment)).Methods("POST")
// Community routes.
r.HandleFunc("/communities/all", useLogin(showAllCommunities)).Methods("GET")
r.HandleFunc("/communities/recent", requireLogin(showRecentCommunities)).Methods("GET")
r.HandleFunc("/communities/search", useLogin(showCommunitySearch)).Methods("GET").Queries("query", "{search}")
r.HandleFunc("/communities/{id:[0-9]+}", useLogin(showCommunity)).Methods("GET")
r.HandleFunc("/communities/{id:[0-9]+}/hot", useLogin(showPopularPosts)).Methods("GET")
r.HandleFunc("/communities/{id:[0-9]+}/posts", requireLogin(createPost)).Methods("POST")
r.HandleFunc("/communities/{id:[0-9]+}/favorite", requireLogin(addCommunityFavorite)).Methods("POST")
r.HandleFunc("/communities/{id:[0-9]+}/unfavorite", requireLogin(deleteCommunityFavorite)).Methods("POST")
// Activiy Feed route.
r.HandleFunc("/activity", requireLogin(showActivityFeed)).Methods("GET")
// Message routes.
r.HandleFunc("/messages", requireLogin(showMessages)).Methods("GET")
r.HandleFunc("/messages", requireLogin(sendMessage)).Methods("POST")
r.HandleFunc("/messages/{id:[0-9]+}/delete", requireLogin(deleteMessage)).Methods("POST")
r.HandleFunc("/messages/{username}", requireLogin(showConversation)).Methods("GET")
r.HandleFunc("/conversations/{id:[0-9]+}", requireLogin(showGroupChat)).Methods("GET")
r.HandleFunc("/conversations/create", requireLogin(showCreateGroupChat)).Methods("GET")
r.HandleFunc("/conversations/create", requireLogin(createGroupChat)).Methods("POST")
r.HandleFunc("/conversations/{id:[0-9]+}/edit", requireLogin(showEditGroupChat)).Methods("GET")
r.HandleFunc("/conversations/{id:[0-9]+}/edit", requireLogin(editGroupChat)).Methods("POST")
r.HandleFunc("/conversations/{id:[0-9]+}/leave", requireLogin(leaveGroupChat)).Methods("POST")
r.HandleFunc("/conversations/{id:[0-9]+}/delete", requireLogin(deleteGroupChat)).Methods("POST")
// Notification routes.
r.HandleFunc("/check_update.json", requireLogin(getNotificationCounts)).Methods("GET")
r.HandleFunc("/notifications", requireLogin(showNotifications)).Methods("GET")
r.HandleFunc("/notifications/friend_requests", requireLogin(showFriendRequests)).Methods("GET")
// Settings routes.
r.HandleFunc("/settings/profile", requireLogin(showProfileSettings)).Methods("GET")
r.HandleFunc("/settings/profile", requireLogin(editProfileSettings)).Methods("POST")
r.HandleFunc("/region", requireLogin(getRegion)).Methods("POST")
r.HandleFunc("/miis", getMii).Methods("POST")
r.HandleFunc("/migrate/{id:[0-9]+}", requireLogin(migratePosts)).Methods("POST")
r.HandleFunc("/rollback/{id:[0-9]+}", requireLogin(rollbackImport)).Methods("POST")
r.HandleFunc("/settings/account", requireLogin(showAccountSettings)).Methods("GET")
r.HandleFunc("/settings/account", requireLogin(editAccountSettings)).Methods("POST")
r.HandleFunc("/blocked", requireLogin(showBlocked)).Methods("GET")
// Help page routes.
r.HandleFunc("/help/rules", useLogin(showRulesPage)).Methods("GET")
r.HandleFunc("/help/faq", useLogin(showFAQPage)).Methods("GET")
r.HandleFunc("/help/legal", useLogin(showLegalPage)).Methods("GET")
r.HandleFunc("/help/contact", useLogin(showContactPage)).Methods("GET")
// Image upload route.
r.HandleFunc("/upload", uploadImage).Methods("POST")
// Admin routes.
r.HandleFunc("/admin", requireLogin(showAdminDashboard)).Methods("GET")
r.HandleFunc("/reports/{id:[0-9]+}/ignore", requireLogin(reportIgnore)).Methods("POST")
r.HandleFunc("/admin/manage", requireLogin(showAdminManagerList)).Methods("GET")
r.HandleFunc("/admin/manage/bantemp", requireLogin(adminBanUser)).Methods("POST")
r.HandleFunc("/admin/manage/unbantemp", requireLogin(adminUnbanUser)).Methods("POST")
//r.HandleFunc("/admin/manage/{table}", requireLogin(showAdminManager)).Methods("GET")
//r.HandleFunc("/admin/manage/{table}/{id:[0-9]+}", requireLogin(showAdminEditor)).Methods("GET", "POST")
r.HandleFunc("/admin/settings", requireLogin(showAdminSettings)).Methods("GET", "POST")
r.HandleFunc("/admin/audit_log", requireLogin(showAdminAuditLog)).Methods("GET")
// Websocket route.
r.HandleFunc("/ws", requireLogin(handleConnections)).Methods("GET")
// Add a 404 page.
r.NotFoundHandler = useLogin(handle404)
// Serve static assets.
r.PathPrefix("/assets/").Handler(http.StripPrefix("/assets/", http.FileServer(http.Dir("assets"))))
// serve images as /images even though this can be changed
r.PathPrefix("/images/").Handler(http.StripPrefix("/images/", http.FileServer(http.Dir("images"))))
if !settings.CSRFProtectDisable {
r.Use(CSRF)
}
if settings.GzipEnabled {
r.Use(gziphandler.GzipHandler)
}
// Tell the http server to handle routing with the router we just made.
http.Handle("/", r)
// Tell the person who started this that we are starting the server.
log.Printf("listening on " + settings.Port)
// Start the server.
if settings.ListenSocket {
// remove tha socket first or else
os.Remove(settings.Port)
unixListener, err := net.Listen("unix", settings.Port)
if err != nil {
log.Fatal("cannot listen on unix socket: ", err)
}
// set socket owner but only if the value is not blank
if settings.SocketOwner != "" {
socketUser, err := osUser.Lookup(settings.SocketOwner)
if err != nil {
log.Fatal("could not look up user so that we can change the owner of the unix socket so that we can listen on it:\n", err)
}
// should probably handle errors here
uidInt, _ := strconv.Atoi(socketUser.Uid)
gidInt, _ := strconv.Atoi(socketUser.Gid)
err = os.Chown(settings.Port, uidInt, gidInt)
if err != nil {
log.Fatal("could not change socket owner", err)
}
}
err = http.Serve(unixListener, nil) // Just serve HTTP requests.
if err != nil {
log.Fatal(err)
}
} else {
if settings.SSL.Enabled && settings.Port != ":80" {
go http.ListenAndServe(":80", http.HandlerFunc(redirect)) // Redirect HTTP requests to the HTTPS site.
err = http.ListenAndServeTLS(settings.Port, settings.SSL.Certificate, settings.SSL.Key, nil)
if err != nil {
log.Fatal(err)
}
} else {
log.Fatal(http.ListenAndServe(settings.Port, nil))
}
}
}

655
structure.sql Normal file
View File

@ -0,0 +1,655 @@
-- MySQL dump 10.15 Distrib 10.0.38-MariaDB, for debian-linux-gnu (x86_64)
--
-- Host: localhost Database: indigo
-- ------------------------------------------------------
-- Server version 10.0.38-MariaDB-0ubuntu0.16.04.1
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
--
-- Table structure for table `admin_notifications`
--
DROP TABLE IF EXISTS `admin_notifications`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `admin_notifications` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`reason` int(11) NOT NULL,
`post` int(11) NOT NULL,
`type` tinyint(1) NOT NULL,
`user` int(11) NOT NULL,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`notif_read` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `audit_log_entries`
--
DROP TABLE IF EXISTS `audit_log_entries`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `audit_log_entries` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`type` tinyint(1) NOT NULL,
`context` int(11) NOT NULL,
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`created_by` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `created_by` (`created_by`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `bans`
--
DROP TABLE IF EXISTS `bans`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `bans` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user` int(11) NOT NULL,
`ip` varchar(42) NOT NULL,
`cidr` tinyint(1) NOT NULL,
`until` datetime NOT NULL,
`ban_by` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `bans_ibfk_1` (`user`),
CONSTRAINT `bans_ibfk_1` FOREIGN KEY (`user`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `blocks`
--
DROP TABLE IF EXISTS `blocks`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `blocks` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`source` int(11) NOT NULL,
`target` int(11) NOT NULL,
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `source` (`source`,`target`),
KEY `blocks_ibfk_2` (`target`),
CONSTRAINT `blocks_ibfk_1` FOREIGN KEY (`source`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `blocks_ibfk_2` FOREIGN KEY (`target`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `comments`
--
DROP TABLE IF EXISTS `comments`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `comments` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`created_by` int(11) NOT NULL,
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`edited_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`feeling` tinyint(1) NOT NULL DEFAULT '0',
`post` int(11) NOT NULL,
`body` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci NOT NULL,
`image` tinytext COLLATE utf8mb4_bin,
`url` varchar(1024) COLLATE utf8mb4_bin NOT NULL,
`is_spoiler` tinyint(1) NOT NULL DEFAULT '0',
`post_type` tinyint(1) DEFAULT '0',
`is_rm` tinyint(1) NOT NULL DEFAULT '0',
`is_rm_by_admin` tinyint(1) NOT NULL DEFAULT '0',
`attachment_type` tinyint(1) NOT NULL DEFAULT '0',
`pinned` tinyint(1) NOT NULL DEFAULT '0',
`url_type` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `created_by` (`created_by`),
KEY `post` (`post`),
CONSTRAINT `comments_ibfk_1` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `comments_ibfk_2` FOREIGN KEY (`post`) REFERENCES `posts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `communities`
--
DROP TABLE IF EXISTS `communities`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `communities` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci NOT NULL,
`description` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci NOT NULL,
`icon` tinytext COLLATE utf8mb4_bin NOT NULL,
`banner` tinytext COLLATE utf8mb4_bin NOT NULL,
`is_featured` tinyint(1) NOT NULL,
`permissions` tinyint(1) NOT NULL DEFAULT '0',
`rm` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `community_favorites`
--
DROP TABLE IF EXISTS `community_favorites`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `community_favorites` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`community` int(11) NOT NULL,
`favorite_by` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `community_favorites_ibfk_1` (`community`),
KEY `community_favorites_ibfk_2` (`favorite_by`),
CONSTRAINT `community_favorites_ibfk_1` FOREIGN KEY (`community`) REFERENCES `communities` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `community_favorites_ibfk_2` FOREIGN KEY (`favorite_by`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `conversations`
--
DROP TABLE IF EXISTS `conversations`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `conversations` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`source` int(11) NOT NULL,
`target` int(11) NOT NULL,
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`is_rm` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `conversations_ibfk_1` (`source`),
CONSTRAINT `conversations_ibfk_1` FOREIGN KEY (`source`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `emotes`
--
DROP TABLE IF EXISTS `emotes`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `emotes` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` text COLLATE utf8mb4_bin NOT NULL,
`image` varchar(1024) COLLATE utf8mb4_bin NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `follows`
--
DROP TABLE IF EXISTS `follows`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `follows` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`follow_to` int(11) NOT NULL,
`follow_by` int(11) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `follow_to` (`follow_to`,`follow_by`),
KEY `follow_by` (`follow_by`),
CONSTRAINT `follows_ibfk_1` FOREIGN KEY (`follow_to`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `follows_ibfk_2` FOREIGN KEY (`follow_by`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `friend_requests`
--
DROP TABLE IF EXISTS `friend_requests`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `friend_requests` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`request_to` int(11) NOT NULL,
`request_by` int(11) NOT NULL,
`message` text NOT NULL,
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`request_read` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `request_to` (`request_to`,`request_by`),
KEY `request_by` (`request_by`),
CONSTRAINT `friend_requests_ibfk_1` FOREIGN KEY (`request_to`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `friend_requests_ibfk_2` FOREIGN KEY (`request_by`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `friendships`
--
DROP TABLE IF EXISTS `friendships`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `friendships` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`source` int(11) NOT NULL,
`target` int(11) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `source` (`source`,`target`),
KEY `target` (`target`),
CONSTRAINT `friendships_ibfk_1` FOREIGN KEY (`target`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `friendships_ibfk_2` FOREIGN KEY (`source`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `group_members`
--
DROP TABLE IF EXISTS `group_members`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `group_members` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user` int(11) NOT NULL,
`conversation` int(11) unsigned NOT NULL,
`unread_messages` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `group_members_ibfk_1` (`user`),
KEY `group_members_ibfk_2` (`conversation`),
CONSTRAINT `group_members_ibfk_1` FOREIGN KEY (`user`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `group_members_ibfk_2` FOREIGN KEY (`conversation`) REFERENCES `conversations` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `images`
--
DROP TABLE IF EXISTS `images`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `images` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`value` varchar(1024) COLLATE utf8mb4_bin NOT NULL,
`hash` char(32) COLLATE utf8mb4_bin NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `imports`
--
DROP TABLE IF EXISTS `imports`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `imports` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user` int(11) NOT NULL,
`migration` int(11) NOT NULL,
`username` varchar(64) COLLATE utf8mb4_bin NOT NULL,
PRIMARY KEY (`id`),
KEY `imports_ibfk_1` (`user`),
KEY `imports_ibfk_2` (`migration`),
CONSTRAINT `imports_ibfk_1` FOREIGN KEY (`user`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `imports_ibfk_2` FOREIGN KEY (`migration`) REFERENCES `migrations` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `login_tokens`
--
DROP TABLE IF EXISTS `login_tokens`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `login_tokens` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`value` varchar(16) COLLATE utf8mb4_bin NOT NULL,
`user` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `login_tokens_ibfk_1` (`user`),
CONSTRAINT `login_tokens_ibfk_1` FOREIGN KEY (`user`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `messages`
--
DROP TABLE IF EXISTS `messages`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `messages` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`created_by` int(11) NOT NULL,
`conversation_id` int(11) unsigned NOT NULL,
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`feeling` tinyint(1) NOT NULL DEFAULT '0',
`body` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci NOT NULL,
`image` tinytext NOT NULL,
`attachment_type` tinyint(1) NOT NULL DEFAULT '0',
`url` varchar(1024) NOT NULL,
`url_type` tinyint(1) NOT NULL DEFAULT '0',
`post_type` tinyint(1) NOT NULL DEFAULT '0',
`is_rm` tinyint(1) NOT NULL DEFAULT '0',
`msg_read` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `created_by` (`created_by`),
KEY `messages_ibfk_2` (`conversation_id`),
CONSTRAINT `messages_ibfk_1` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `messages_ibfk_2` FOREIGN KEY (`conversation_id`) REFERENCES `conversations` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `migrated_communities`
--
DROP TABLE IF EXISTS `migrated_communities`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `migrated_communities` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`migrated_id` varchar(64) COLLATE utf8mb4_bin NOT NULL,
`icon` varchar(1024) COLLATE utf8mb4_bin NOT NULL,
`title` text COLLATE utf8mb4_bin NOT NULL,
`migration` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `migrations`
--
DROP TABLE IF EXISTS `migrations`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `migrations` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`image` varchar(1024) COLLATE utf8mb4_bin NOT NULL,
`script` varchar(1024) COLLATE utf8mb4_bin NOT NULL,
`url` varchar(1024) COLLATE utf8mb4_bin NOT NULL,
`is_rm` tinyint(1) NOT NULL DEFAULT '0',
`password_required` tinyint(1) NOT NULL DEFAULT '1',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `notifications`
--
DROP TABLE IF EXISTS `notifications`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `notifications` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`notif_type` tinyint(1) NOT NULL,
`notif_by` int(11) DEFAULT NULL,
`notif_to` int(11) NOT NULL,
`notif_post` int(11) DEFAULT NULL,
`merged` int(11) DEFAULT NULL,
`notif_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`notif_read` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `notif_read` (`notif_read`),
KEY `merged` (`merged`),
KEY `notif_to` (`notif_to`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `options`
--
DROP TABLE IF EXISTS `options`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `options` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`post` int(11) NOT NULL,
`name` varchar(64) COLLATE utf8mb4_bin DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `options_ibfk_1` (`post`),
CONSTRAINT `options_ibfk_1` FOREIGN KEY (`post`) REFERENCES `posts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `password_resets`
--
DROP TABLE IF EXISTS `password_resets`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `password_resets` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`token` varchar(16) COLLATE utf8mb4_bin NOT NULL,
`user` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `password_resets_ibfk_1` (`user`),
CONSTRAINT `password_resets_ibfk_1` FOREIGN KEY (`user`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `posts`
--
DROP TABLE IF EXISTS `posts`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `posts` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`created_by` int(11) NOT NULL,
`community_id` int(11) NOT NULL,
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`edited_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`feeling` tinyint(1) NOT NULL DEFAULT '0',
`body` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci NOT NULL,
`image` tinytext COLLATE utf8mb4_bin,
`attachment_type` tinyint(1) NOT NULL DEFAULT '0',
`url` varchar(1024) COLLATE utf8mb4_bin NOT NULL,
`is_spoiler` tinyint(1) NOT NULL DEFAULT '0',
`is_rm` tinyint(1) NOT NULL DEFAULT '0',
`is_rm_by_admin` tinyint(1) NOT NULL DEFAULT '0',
`post_type` tinyint(1) NOT NULL DEFAULT '0',
`migration` int(11) NOT NULL DEFAULT '0',
`import_id` int(11) NOT NULL DEFAULT '0',
`migrated_id` varchar(64) COLLATE utf8mb4_bin NOT NULL DEFAULT '',
`migrated_community` varchar(32) COLLATE utf8mb4_bin NOT NULL DEFAULT '',
`pinned` tinyint(1) NOT NULL DEFAULT '0',
`privacy` tinyint(1) NOT NULL DEFAULT '0',
`url_type` tinyint(1) NOT NULL DEFAULT '0',
`repost` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `created_by` (`created_by`),
KEY `community_id` (`community_id`),
CONSTRAINT `posts_ibfk_1` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `posts_ibfk_2` FOREIGN KEY (`community_id`) REFERENCES `communities` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `profiles`
--
DROP TABLE IF EXISTS `profiles`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `profiles` (
`user` int(11) NOT NULL AUTO_INCREMENT,
`comment` text COLLATE utf8mb4_bin NOT NULL,
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`nnid` varchar(16) COLLATE utf8mb4_bin NOT NULL,
`mh` varchar(13) COLLATE utf8mb4_bin NOT NULL DEFAULT '',
`region` varchar(64) COLLATE utf8mb4_bin NOT NULL,
`gender` int(1) NOT NULL,
`nnid_visibility` tinyint(1) NOT NULL,
`yeah_visibility` tinyint(1) NOT NULL,
`reply_visibility` tinyint(1) NOT NULL,
`discord` varchar(37) COLLATE utf8mb4_bin NOT NULL DEFAULT '',
`steam` varchar(64) COLLATE utf8mb4_bin NOT NULL DEFAULT '',
`psn` varchar(16) COLLATE utf8mb4_bin NOT NULL DEFAULT '',
`switch_code` varchar(17) COLLATE utf8mb4_bin NOT NULL DEFAULT '',
`twitter` varchar(15) COLLATE utf8mb4_bin NOT NULL DEFAULT '',
`youtube` varchar(1024) COLLATE utf8mb4_bin NOT NULL DEFAULT '',
`allow_friend` tinyint(1) NOT NULL DEFAULT '1',
`favorite` int(11) NOT NULL DEFAULT '0',
`avatar_image` varchar(1024) COLLATE utf8mb4_bin NOT NULL DEFAULT '',
`avatar_id` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`user`),
CONSTRAINT `profiles_ibfk_1` FOREIGN KEY (`user`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `reports`
--
DROP TABLE IF EXISTS `reports`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `reports` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`type` tinyint(1) NOT NULL,
`pid` int(11) NOT NULL,
`message` varchar(100) COLLATE utf8mb4_bin NOT NULL,
`user` int(11) NOT NULL,
`reason` int(11) NOT NULL,
`is_rm` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `reports_ibfk_1` (`user`),
CONSTRAINT `reports_ibfk_1` FOREIGN KEY (`user`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `roles`
--
DROP TABLE IF EXISTS `roles`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `roles` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`image` varchar(1024) COLLATE utf8mb4_bin NOT NULL,
`organization` varchar(32) COLLATE utf8mb4_bin NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `sessions`
--
DROP TABLE IF EXISTS `sessions`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `sessions` (
`id` text COLLATE utf8mb4_bin NOT NULL,
`user` int(11) NOT NULL,
KEY `sessions_ibfk_1` (`user`),
CONSTRAINT `sessions_ibfk_1` FOREIGN KEY (`user`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `users`
--
DROP TABLE IF EXISTS `users`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci NOT NULL,
`nickname` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci NOT NULL,
`avatar` tinytext COLLATE utf8mb4_bin NOT NULL,
`email` tinytext COLLATE utf8mb4_bin,
`password` varchar(75) COLLATE utf8mb4_bin NOT NULL,
`ip` varchar(39) COLLATE utf8mb4_bin NOT NULL,
`level` int(2) NOT NULL,
`role` int(11) NOT NULL,
`online` tinyint(1) NOT NULL DEFAULT '0',
`last_seen` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`hide_last_seen` tinyint(1) NOT NULL DEFAULT '0',
`color` varchar(7) COLLATE utf8mb4_bin NOT NULL DEFAULT '',
`theme` varchar(31) COLLATE utf8mb4_bin NOT NULL DEFAULT '',
`yeah_notifications` tinyint(1) NOT NULL,
`has_mh` tinyint(1) NOT NULL DEFAULT '0',
`hide_online` tinyint(1) NOT NULL DEFAULT '0',
`group_permissions` tinyint(1) NOT NULL DEFAULT '0',
`websockets_enabled` tinyint(1) NOT NULL DEFAULT '1',
`forbidden_keywords` longtext COLLATE utf8mb4_bin NOT NULL,
`default_privacy` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `votes`
--
DROP TABLE IF EXISTS `votes`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `votes` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`option_id` int(11) NOT NULL,
`user` int(11) NOT NULL,
`poll` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `votes_ibfk_1` (`poll`),
KEY `votes_ibfk_2` (`user`),
CONSTRAINT `votes_ibfk_2` FOREIGN KEY (`user`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `yeahs`
--
DROP TABLE IF EXISTS `yeahs`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `yeahs` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`yeah_post` int(11) NOT NULL,
`yeah_by` int(11) NOT NULL,
`on_comment` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `yeah_post` (`yeah_post`,`yeah_by`,`on_comment`),
KEY `yeah_by` (`yeah_by`),
CONSTRAINT `yeahs_ibfk_1` FOREIGN KEY (`yeah_by`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2019-02-17 21:42:15

492
types.go Normal file
View File

@ -0,0 +1,492 @@
// All the types/structures defined in Indigo.
package main
import (
"database/sql"
"html/template"
"sync"
"time"
)
// Variable declarations for admin settings.
type adminConfig struct {
Manage struct {
MinimumLevel int
}
Settings struct {
MinimumLevel int
}
}
// Variable declarations for comments.
type comment struct {
ID int
CreatedBy int
PostID int
CreatedAt string
CreatedAtUnix int64
EditedAt string
EditedAtUnix int64
Feeling int
BodyText string
Body template.HTML
Image string
AttachmentType int
URL string
URLType int
Pinned bool
IsSpoiler bool
IsRMByAdmin bool
PostType int
CommenterUsername string
CommenterNickname string
CommenterIcon string
CommenterHasMii bool
CommenterOnline bool
CommenterHideOnline bool
CommenterColor string
CommenterRoleImage string
CommenterRoleOrganization string
Yeahed bool
YeahCount int
ByMe bool
ByMii bool
CanYeah bool
}
// Variable declarations for communities.
type community struct {
ID int
Title string
DescriptionText string
Description template.HTML
Icon string
Banner string
IsFeatured bool
Permissions int
RM bool
}
// Variable declarations for settings.
type config struct {
// if this is true, then it will listen on a unix socket instead of a tcp port
ListenSocket bool
// this can be left blank if you are listening and accessing as the same user
SocketOwner string
// if listensocket is true then port is the socket
Port string
// whether to enable gzip compression
// unnecessary with a reverse proxy, absolutely necessary without one
GzipEnabled bool
// will disable csrf protection which you probably want to do because it's annoying
CSRFProtectDisable bool
SSL struct {
Enabled bool
Certificate string
Key string
}
DB struct {
Host string
Username string
Password string
Name string
}
ImageHost struct {
Provider string
Username string
APIEndpoint string
APIPublic string
APISecret string
ImageEndpoint string
UploadPreset string
MaxUploadSize string
}
Webhooks struct {
Enabled bool
Reports string
Signups string
Logins string
}
ReCAPTCHA struct {
Enabled bool
SiteKey string
SecretKey string
}
SMTP struct {
Enabled bool
Hostname string
Port string
Email string
Password string
}
CSRFSecret string
IPHubKey string
MiiEndpointPrefix string
Proxy bool
ForceLogins bool
AllowSignups bool
DefaultTimezone string
ReportReasons []reportReason
TextToReplace []struct {
Original string
Replaced string
}
EmoteLimit int
}
// Variable declarations for conversations.
type conversation struct {
ID int
Target int
Nickname string
Username string
Online bool
HideOnline bool
Color string
Icon string
HasMii bool
RoleImage string
CreatedBy int
BodyText string
Body template.HTML
Image string
PostType int
Date string
DateUnix int64
Read bool
}
// Variable declarations for friend requests.
type friendRequest struct {
ID int
By int
CreatedAt string
CreatedAtUnix int64
Date string
Message string
Read bool
ByUsername string
ByAvatar string
ByHasMii bool
ByNickname string
ByOnline bool
ByHideOnline bool
ByColor string
ByRoleImage string
ByRoleOrganization string
}
// Variable declarations for import log entries.
type importLog struct {
ID int
Image string
Username string
}
// Variable declarations for messages.
type message struct {
ID int
Date string
DateUnix int64
Feeling int
BodyText string
Body template.HTML
Image string
AttachmentType int
URL string
URLType int
PostType int
ByUsername string
ByAvatar string
ByHasMii bool
ByOnline bool
ByHideOnline bool
ByColor string
ByRoleImage string
ByMe bool
}
// Variable declarations for migrations.
type migration struct {
Success int `json:"success"`
Error string `json:"error"`
Posts []struct {
ID interface{} `json:"id"`
CreatedBy interface{} `json:"created_by"`
CommunityID interface{} `json:"community_id"`
CreatedAt string `json:"created_at"`
EditedAt string `json:"edited_at"`
Feeling int `json:"feeling"`
Body string `json:"body"`
Image string `json:"image"`
AttachmentType int `json:"attachment_type"`
URL string `json:"url"`
IsSpoiler interface{} `json:"is_spoiler"`
IsRM int `json:"is_rm"`
IsRMByAdmin int `json:"is_rm_by_admin"`
PostType int `json:"post_type"`
} `json:"posts"`
Communities []struct {
ID interface{} `json:"id"`
Title string `json:"name"`
Icon string `json:"icon"`
} `json:"communities"`
}
// Variable declarations for the migration options.
type migrationOption struct {
ID int
Image string
PasswordRequired bool
}
// Variable declarations for notifications.
type notification struct {
ID int
Type int
By int
Post sql.NullInt64
Date string
DateUnix int64
Read bool
MergedCount int
MergedOthers int
URL string
ByUsername string
ByAvatar string
ByHasMii bool
ByNickname string
ByOnline bool
ByHideOnline bool
ByColor string
ByRoleImage string
PostText string
PostType int
PostIsRM bool
MergedUsername [3]string
MergedNickname [3]string
MergedColor [3]string
}
// Variable declarations for notification counts.
type notificationCount struct {
Messages int
Notifications int
}
// Variable declarations for poll options.
type option struct {
ID int
Name string
Votes float64
Percentage float64
Selected bool
}
// Variable declarations for polls.
type poll struct {
ID int
Votes float64
Options []option
Selected bool
}
// Variable declarations for posts.
type post struct {
ID int
Type int
CreatedBy int
CreatedAt string
CreatedAtTime time.Time
CreatedAtUnix int64
EditedAt string
EditedAtTime time.Time
EditedAtUnix int64
Feeling int
Body template.HTML
BodyText string
Image string
AttachmentType int
URL string
URLType int
Pinned bool
Privacy int
IsSpoiler bool
IsRM bool
IsRMByAdmin bool
ByMe bool
Poll poll
HasPoll bool
PostType int
Repost *post
RepostID int
MigratedID string
MigratedCommunity string
MigrationID int
MigrationImage string
MigrationURL string
PosterUsername string
PosterNickname string
PosterIcon string
PosterHasMii bool
PosterOnline bool
PosterHideOnline bool
PosterColor string
PosterRoleID int
PosterRoleImage string
PosterRoleOrganization string
CommunityID int
CommunityName string
CommunityIcon string
CommunityRM bool
Yeahed bool
CanYeah bool
YeahCount int
CommentCount int
CommentPreview comment
}
// Variable declarations for profiles.
type profile struct {
User int
CreatedAt string
CreatedAtUnix int64
NNID string
MiiHash string
AvatarImage string
AvatarID int
Gender string
Region string
Discord string
Twitter string
SwitchCode string
PSN string
YouTube string
Steam string
AllowFriend int
CommentText string
Comment template.HTML
NNIDVisibility int
YeahVisibility int
ReplyVisibility int
FavoritePostID int
FavoritePostImage string
FriendCount int
FollowingCount int
FollowerCount int
PostCount int
CommentCount int
YeahCount int
}
// Variable declarations for profile sidebars.
type profileSidebar struct {
User user
CurrentUser user
Profile profile
ProfileOnPage string
IsFollowing bool
IsFollowingMe bool
Reasons []reportReason
FavoriteCommunities []community
FriendStatus int
Request friendRequest
RequestTime string
}
// Variable declarations for reports.
type report struct {
ID int
Type int
Message string
Reason int
ByID int
ByUsername string
ByNickname string
ByColor string
Post *post
User *user
}
// Variable declarations for report reasons.
type reportReason struct {
Name string
Message string
Enabled bool
BodyRequired bool
}
// Variable declarations for repost previews.
type repostPreview struct {
ID int
Nickname string
Text string
PostType int
}
// Variable declarations for users.
type user struct {
ID int
Username string
Nickname string
Avatar string
HasMii bool
Email string
Password string
IP string
Level int
Role struct {
Image string
Organization string
}
Online bool
HideOnline bool
Color string
Theme string
ThemeColors []string
LastSeen string
LastSeenUnix int64
HideLastSeen bool
Comment string
YeahNotifications bool
LightMode bool
WebsocketsEnabled bool
DefaultPrivacy int
Blocked bool
Timezone string
Notifications notificationCount
ForbiddenKeywords string
CSRFToken string
}
// Variable declarations for websocket messages.
type wsMessage struct {
Type string `json:"type"`
ID string `json:"id"`
Content string `json:"content"`
}
// Variable declarations for websocket sessions.
type wsSession struct {
Mutex *sync.Mutex
Connected bool
UserID int
Level int
OnPage string
Send chan wsMessage
}
// Variable declarations for Yeahs.
type yeah struct {
ID int
Username string
Avatar string
HasMii bool
Role string
}
type iphubBlockResponse struct {
Block int8 `json:"block"`
ASN uint16 `json:"asn""`
}

906
utils.go Normal file
View File

@ -0,0 +1,906 @@
// Various utility functions used in Indigo.
package main
import (
// Internals
"database/sql"
"encoding/json"
"fmt"
"html/template"
"io/ioutil"
"math"
"math/rand"
"net"
"net/http"
"regexp"
"strconv"
"strings"
"time"
"unicode/utf8"
// Externals
"github.com/gorilla/csrf"
"github.com/gorilla/websocket"
sessions "github.com/kataras/go-sessions/v3"
"github.com/microcosm-cc/bluemonday"
"github.com/russross/blackfriday/v2"
)
// Inititialize sessions and other variables. Used in almost every page that uses HTML, and even some that don't.
func doSession(w http.ResponseWriter, r *http.Request) (user, bool) {
session := sessions.Start(w, r)
currentUser := user{}
ip := getIP(r)
timezone, err := r.Cookie("timezone")
if err != nil || len(timezone.Value) == 0 {
timezone = &http.Cookie{Name: "timezone", Value: getTimezone(ip), Expires: time.Now().Add(365 * 24 * time.Hour)}
http.SetCookie(w, timezone)
}
currentUser.Timezone = timezone.Value
host, _, _ := net.SplitHostPort(ip)
cidr := getCIDR(host, "1")
cidr2 := getCIDR(host, "2")
var banLength time.Time
db.QueryRow("SELECT until FROM bans WHERE (cidr = 0 AND ip = ?) OR (cidr = 1 AND ip = ?) OR (cidr = 2 AND ip = ?)", host, cidr, cidr2).Scan(&banLength)
if int64(banLength.Unix()) != -62135596800 {
success := showBan(w, currentUser, banLength)
if success {
return currentUser, false
}
}
if len(session.GetString("username")) != 0 {
currentUser = QueryUser(session.GetString("username"), currentUser.Timezone)
if len(currentUser.Theme) > 0 {
currentUser.ThemeColors = strings.Split(currentUser.Theme, ",")
}
currentUser.Avatar = getAvatar(currentUser.Avatar, currentUser.HasMii, 0)
db.QueryRow("SELECT until FROM bans WHERE user = ?", currentUser.ID).Scan(&banLength)
if int64(banLength.Unix()) != -62135596800 {
success := showBan(w, currentUser, banLength)
if success {
return currentUser, false
}
}
} else {
indigoAuth, err := r.Cookie("indigo-auth")
if err == nil && len(indigoAuth.Value) > 0 {
var username string
db.QueryRow("SELECT username FROM login_tokens LEFT JOIN users ON user = users.id WHERE value = ?", &indigoAuth.Value).Scan(&username)
if len(username) > 0 {
currentUser = QueryUser(username, currentUser.Timezone)
if len(currentUser.Theme) > 0 {
currentUser.ThemeColors = strings.Split(currentUser.Theme, ",")
}
currentUser.Avatar = getAvatar(currentUser.Avatar, currentUser.HasMii, 0)
session.Set("username", currentUser.Username)
session.Set("user_id", currentUser.ID)
stmt, err := db.Prepare("INSERT INTO sessions (id, user) VALUES (?, ?)")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return currentUser, false
}
stmt.Exec(session.ID(), currentUser.ID)
stmt.Close()
db.QueryRow("SELECT until FROM bans WHERE user = ?", currentUser.ID).Scan(&banLength)
if int64(banLength.Unix()) != -62135596800 {
success := showBan(w, currentUser, banLength)
if success {
return currentUser, false
}
}
} else {
if settings.ForceLogins && r.URL.Path != "/reset" {
http.Redirect(w, r, "/login", 301)
return currentUser, false
}
return currentUser, true
}
} else {
if settings.ForceLogins && r.URL.Path != "/reset" {
http.Redirect(w, r, "/login", 301)
return currentUser, false
}
return currentUser, true
}
}
currentUser.CSRFToken = csrf.Token(r)
currentUser.LightMode = getLightMode(w, r)
if r.Header.Get("X-PJAX") == "" {
var friendRequests int
db.QueryRow("SELECT COUNT(*) FROM messages LEFT JOIN conversations ON conversation_id = conversations.id WHERE (source = ? OR target = ?) AND created_by <> ? AND msg_read = 0 AND messages.is_rm = 0 AND conversations.is_rm = 0", currentUser.ID, currentUser.ID, currentUser.ID).Scan(&currentUser.Notifications.Messages)
var groupUnread int
db.QueryRow("SELECT SUM(unread_messages) FROM group_members WHERE user = ?", currentUser.ID).Scan(&groupUnread)
currentUser.Notifications.Messages += groupUnread
db.QueryRow("SELECT COUNT(*) FROM notifications WHERE notif_to = ? AND merged IS NULL AND notif_read = 0", currentUser.ID).Scan(&currentUser.Notifications.Notifications)
db.QueryRow("SELECT COUNT(*) FROM friend_requests WHERE request_to = ? AND request_read = 0", currentUser.ID).Scan(&friendRequests)
currentUser.Notifications.Notifications += friendRequests
}
return currentUser, true
}
// Escape the "forbidden keywords" field for regexp.
func escapeForbiddenKeywords(regex string) string {
if len(regex) == 0 {
return "b\bb" // This regex always returns "false", so no posts are ever filtered out if you don't have any reserved words.
}
regex = regexp.QuoteMeta(regex)
split := strings.Split(regex, ",")
fixed := []string{}
for _, s := range split {
if len(s) > 0 {
fixed = append(fixed, s)
}
}
regex = strings.Join(fixed, "|")
regex = strings.Replace(regex, "\\|", ",", -1)
return strings.Replace(regex, "\\\\", "\\", -1)
}
// Escape Markdown.
func escapeMarkdown(text string) string {
text = string(symbols.ReplaceAll([]byte(text), []byte("\\$1")))
return text
}
// Get a CIDR-esque range from an IP.
func getCIDR(ip string, cidr string) string {
netmasks := strings.Split(ip, ".")
netmasks[3] = "0"
if cidr == "2" {
netmasks[2] = "0"
}
return strings.Join(netmasks, ".")
}
// Get the user's light mode status.
func getLightMode(w http.ResponseWriter, r *http.Request) bool {
lightMode, err := r.Cookie("light")
if err != nil || len(lightMode.Value) == 0 {
lightMode = &http.Cookie{Name: "light", Value: "false", Expires: time.Unix(253402300799, 0)}
http.SetCookie(w, lightMode)
}
lightModeBool, err := strconv.ParseBool(lightMode.Value)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return false
}
return lightModeBool
}
// Get the data of a post's migration.
func getPostMigration(migration int, migratedCommunity string) (string, string, string, string) {
var migrationImage string
var migrationURL string
err := db.QueryRow("SELECT image, url FROM migrations WHERE id = ?", migration).Scan(&migrationImage, &migrationURL)
if err != nil {
fmt.Println("no migrations")
fmt.Println(err.Error())
return "https://i.ytimg.com/vi/DkIVqD8pJt8/maxresdefault.jpg", "http://marios-princess-sex.ga/#", "This message should not appear. An error occurred while grabbing the migration data. Check the console.", "https://closed.pizza/s/img/title-icon-default.png"
}
var communityTitle string
var communityIcon string
err = db.QueryRow("SELECT title, icon FROM migrated_communities WHERE migrated_id = ? AND migration = ?", migratedCommunity, migration).Scan(&communityTitle, &communityIcon)
if err != nil {
return migrationImage, migrationURL, "Unknown Community", "/assets/img/title-icon-default.png"
}
return migrationImage, migrationURL, communityTitle, communityIcon
}
// Format timestamps in a way that normal people who AREN'T robots can read.
func humanTiming(timestamp time.Time, timezone string) string {
location, err := time.LoadLocation(timezone)
if err != nil {
fmt.Println(err.Error())
return err.Error()
}
timestamp = timestamp.In(location)
since := time.Now().In(location).Sub(timestamp).Seconds()
if since <= 1 {
return "Less than a second ago"
} else if since < 2 {
return "1 second ago"
} else if since < 60 {
return strconv.Itoa(int(math.Floor(since))) + " seconds ago"
} else if since < 120 {
return "1 minute ago"
} else if since < 3600 {
return strconv.Itoa(int(math.Floor(since/60))) + " minutes ago"
} else if since < 7200 {
return "1 hour ago"
} else if since < 86400 {
return strconv.Itoa(int(math.Floor(since/60/60))) + " hours ago"
} else if since < 172800 {
return "1 day ago"
} else if since < 345600 {
return strconv.Itoa(int(math.Floor(since/60/60/24))) + " days ago"
} else {
return timestamp.Format("01/02/2006 3:04 PM")
}
/* Discord styled timestamp code here.
now := time.Now().In(location)
if now.Day() == timestamp.Day() && now.Month() == timestamp.Month() && now.Year() == timestamp.Year() {
return timestamp.Format("Today at 3:04 PM")
} else if now.Day()-1 == timestamp.Day() && now.Month() == timestamp.Month() && now.Year() == timestamp.Year() {
return timestamp.Format("Yesterday at 3:04 PM")
} else if now.Day()-2 == timestamp.Day() && now.Month() == timestamp.Month() && now.Year() == timestamp.Year() {
return timestamp.Format("Last Monday at 3:04 PM")
} else {
return timestamp.Format("01/02/2006 3:04 PM")
}
*/
}
// Send a notification to a user.
func createNotif(to int, notif_type int, post string, currentUser int) {
notif_read := 0
for client := range clients {
if clients[client].UserID == to {
if ((notif_type == 0 || notif_type == 2 || notif_type == 3) && clients[client].OnPage == "/posts/"+post) || (notif_type == 1 && clients[client].OnPage == "/comments/"+post) {
notif_read = 1
break
}
}
}
if notif_type == 0 || notif_type == 1 {
var hasYeahNotificationsEnabled bool
db.QueryRow("SELECT yeah_notifications FROM users WHERE id = ?", to).Scan(&hasYeahNotificationsEnabled)
if !hasYeahNotificationsEnabled {
return
}
}
// 0 = post yeah, 1 = reply yeah, 2 = comment on your post, 3 = poster's comment, 4 = follow
var check_mergedusernews int
if notif_type != 4 {
db.QueryRow("SELECT merged FROM notifications WHERE notif_by = ? AND notif_to = ? AND notif_type = ? AND notif_post = ? AND merged IS NOT NULL AND notif_date > NOW() - 28800 ORDER BY notif_date DESC", currentUser, to, notif_type, post).Scan(&check_mergedusernews)
} else {
db.QueryRow("SELECT merged FROM notifications WHERE notif_by = ? AND notif_to = ? AND notif_type = ? AND merged IS NOT NULL AND notif_date > NOW() - 28800 ORDER BY notif_date DESC", currentUser, to, notif_type).Scan(&check_mergedusernews)
}
if check_mergedusernews != 0 {
stmt, _ := db.Prepare("UPDATE notifications SET notif_read = 0, notif_date = CURRENT_TIMESTAMP WHERE id = ?")
stmt.Exec(&check_mergedusernews)
stmt.Close()
} else {
var result_update_newsmergesearch int
if notif_type != 4 {
db.QueryRow("SELECT id FROM notifications WHERE notif_to = ? AND notif_post = ? AND notif_date > NOW() - 28800 AND notif_type = ? ORDER BY notif_date DESC", to, post, notif_type).Scan(&result_update_newsmergesearch)
} else {
db.QueryRow("SELECT id FROM notifications WHERE notif_to = ? AND notif_date > NOW() - 28800 AND notif_type = ? ORDER BY notif_date DESC", to, notif_type).Scan(&result_update_newsmergesearch)
}
if result_update_newsmergesearch != 0 {
if notif_type != 4 {
stmt, _ := db.Prepare("INSERT INTO notifications(notif_by, notif_to, notif_post, merged, notif_type, notif_read) VALUES (?, ?, ?, ?, ?, ?)")
stmt.Exec(currentUser, to, post, result_update_newsmergesearch, notif_type, notif_read)
stmt.Close()
} else {
stmt, _ := db.Prepare("INSERT INTO notifications(notif_by, notif_to, merged, notif_type, notif_read) VALUES (?, ?, ?, ?, ?)")
stmt.Exec(currentUser, to, result_update_newsmergesearch, notif_type, notif_read)
stmt.Close()
}
stmt, _ := db.Prepare("UPDATE notifications SET notif_read = ?, notif_date = NOW() WHERE id = ?")
stmt.Exec(notif_read, result_update_newsmergesearch)
stmt.Close()
} else {
if notif_type != 4 {
stmt, _ := db.Prepare("INSERT INTO notifications(notif_by, notif_to, notif_post, notif_type, notif_read) VALUES (?, ?, ?, ?, ?)")
stmt.Exec(currentUser, to, post, notif_type, notif_read)
stmt.Close()
} else {
stmt, _ := db.Prepare("INSERT INTO notifications(notif_by, notif_to, notif_type, notif_read) VALUES (?, ?, ?, ?)")
stmt.Exec(currentUser, to, notif_type, notif_read)
stmt.Close()
}
}
}
if notif_read == 0 {
var msg wsMessage
msg.Type = "notif"
var notifCount int
var friendRequests int
db.QueryRow("SELECT COUNT(*) FROM notifications WHERE notif_to = ? AND merged IS NULL AND notif_read = 0", &to).Scan(&notifCount)
db.QueryRow("SELECT COUNT(*) FROM friend_requests WHERE request_to = ? AND request_read = 0", to).Scan(&friendRequests)
msg.Content = strconv.Itoa(notifCount + friendRequests)
for client := range clients {
if clients[client].UserID == to {
err := client.WriteJSON(msg)
if err != nil {
client.Close()
delete(clients, client)
}
}
}
}
}
// Find a user with a username.
func QueryUser(username string, timezone string) user {
var users = user{}
var role int
var lastSeenTime time.Time
db.QueryRow("SELECT id, username, nickname, avatar, has_mh, email, password, ip, level, role, online, hide_online, last_seen, hide_last_seen, color, theme, yeah_notifications, websockets_enabled, forbidden_keywords, default_privacy FROM users WHERE username=?", username).Scan(&users.ID, &users.Username, &users.Nickname, &users.Avatar, &users.HasMii, &users.Email, &users.Password, &users.IP, &users.Level, &role, &users.Online, &users.HideOnline, &lastSeenTime, &users.HideLastSeen, &users.Color, &users.Theme, &users.YeahNotifications, &users.WebsocketsEnabled, &users.ForbiddenKeywords, &users.DefaultPrivacy)
if role > 0 {
db.QueryRow("SELECT image, organization FROM roles WHERE id = ?", role).Scan(&users.Role.Image, &users.Role.Organization)
}
users.Timezone = timezone
users.LastSeen = humanTiming(lastSeenTime, timezone)
users.LastSeenUnix = lastSeenTime.Unix()
return users
}
// Send JSON to a websocket.
//func sendJSON()
// Get an array of posts from an SQL query.
func setupPost(row *post, currentUser user, postType int, repostLayer int) *post {
row.PosterIcon = getAvatar(row.PosterIcon, row.PosterHasMii, row.Feeling)
if row.PosterRoleID > 0 {
row.PosterRoleImage = getRoleImage(row.PosterRoleID)
}
row.CreatedAt = humanTiming(row.CreatedAtTime, currentUser.Timezone)
row.CreatedAtUnix = row.CreatedAtTime.Unix()
if row.EditedAtTime.Sub(row.CreatedAtTime).Minutes() > 5 {
row.EditedAt = humanTiming(row.EditedAtTime, currentUser.Timezone)
row.EditedAtUnix = row.EditedAtTime.Unix()
}
if len(row.MigratedID) == 0 || strings.Contains(row.BodyText, ":markdown:") {
row.Body = parseBodyWithLineBreaks(row.BodyText, true, true)
} else {
row.Body = parseBodyWithLineBreaks(row.BodyText, true, false)
}
if row.CreatedBy == currentUser.ID {
row.ByMe = true
}
if row.PostType == 2 {
row.Poll = getPoll(row.ID, currentUser.ID)
}
row.Type = postType
if row.RepostID > 0 {
var repost post
if repostLayer < 3 {
db.QueryRow("SELECT posts.id, created_by, created_at, edited_at, feeling, body, image, attachment_type, is_spoiler, post_type, url, url_type, pinned, privacy, repost, migration, migrated_id, migrated_community, is_rm_by_admin, communities.id, title, icon, rm, username, nickname, avatar, has_mh, online, hide_online, color, role FROM posts LEFT JOIN communities ON communities.id = community_id LEFT JOIN users ON users.id = created_by WHERE posts.id = ? AND is_rm = 0 AND users.id NOT IN (SELECT if(source = ?, target, source) FROM blocks WHERE (source = ? AND target = users.id) OR (source = users.id AND target = ?)) AND IF(created_by = ?, true, LOWER(body) NOT REGEXP LOWER(?)) AND (privacy = 0 OR (privacy IN (1, 2, 3, 4) AND (SELECT COUNT(*) FROM friendships WHERE source = ? AND target = created_by OR source = created_by AND target = ? LIMIT 1) = 1) OR (privacy IN (1, 3, 5, 6) AND (SELECT COUNT(*) FROM follows WHERE follow_to = created_by AND follow_by = ? LIMIT 1) = 1) OR (privacy IN (1, 2, 5, 7) AND (SELECT COUNT(*) FROM follows WHERE follow_to = ? AND follow_by = created_by) = 1) OR (privacy = 8 AND ? > 0) OR created_by = ?) LIMIT 1", row.RepostID, currentUser.ID, currentUser.ID, currentUser.ID, currentUser.ID, escapeForbiddenKeywords(currentUser.ForbiddenKeywords), currentUser.ID, currentUser.ID, currentUser.ID, currentUser.ID, currentUser.Level, currentUser.ID).Scan(&repost.ID, &repost.CreatedBy, &repost.CreatedAtTime, &repost.EditedAtTime, &repost.Feeling, &repost.BodyText, &repost.Image, &repost.AttachmentType, &repost.IsSpoiler, &repost.PostType, &repost.URL, &repost.URLType, &repost.Pinned, &repost.Privacy, &repost.RepostID, &repost.MigrationID, &repost.MigratedID, &repost.MigratedCommunity, &repost.IsRMByAdmin, &repost.CommunityID, &repost.CommunityName, &repost.CommunityIcon, &repost.CommunityRM, &repost.PosterUsername, &repost.PosterNickname, &repost.PosterIcon, &repost.PosterHasMii, &repost.PosterOnline, &repost.PosterHideOnline, &repost.PosterColor, &repost.PosterRoleID)
row.Repost = &repost
row.Repost.Type = 3
if len(row.Repost.CommunityName) > 0 {
repostLayer = repostLayer + 1
row.Repost = setupPost(row.Repost, currentUser, 3, repostLayer)
}
} else {
repost.Type = 4
repost.ID = row.ID
row.Repost = &repost
}
}
if row.MigrationID > 0 {
row.MigrationImage, row.MigrationURL, row.CommunityName, row.CommunityIcon = getPostMigration(row.MigrationID, row.MigratedCommunity)
}
db.QueryRow("SELECT COUNT(*) FROM yeahs WHERE yeah_post = ? AND yeah_by = ? AND on_comment = 0 LIMIT 1", row.ID, currentUser.ID).Scan(&row.Yeahed)
row.CanYeah = checkIfCanYeah(currentUser, row.CreatedBy)
if row.CommentCount != -1 {
db.QueryRow("SELECT COUNT(*) FROM yeahs WHERE yeah_post = ? AND on_comment = 0", row.ID).Scan(&row.YeahCount)
db.QueryRow("SELECT COUNT(*) FROM comments WHERE post = ? AND is_rm = 0", row.ID).Scan(&row.CommentCount)
if row.CommentCount > 0 && postType != -1 && postType != 3 {
row.CommentPreview = getCommentPreview(row.ID, currentUser)
}
} else {
db.QueryRow("SELECT COUNT(*) FROM yeahs WHERE yeah_post = ? AND on_comment = 1", row.ID).Scan(&row.YeahCount)
}
return row
}
// Show a ban screen.
func showBan(w http.ResponseWriter, currentUser user, banLength time.Time) bool {
if time.Now().Sub(banLength).Seconds() > 1 {
stmt, _ := db.Prepare("DELETE FROM bans WHERE user = ?")
stmt.Exec(currentUser.ID)
stmt.Close()
return false
} else {
var data = map[string]interface{}{
"CurrentUser": currentUser,
"Length": banLength.Format("01/02/2006 3:04 PM"),
"LengthForever": banLength.Year() > 2100,
}
err := templates.ExecuteTemplate(w, "ban.html", data)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
return true
}
}
// Find a profile by user ID.
func QueryProfile(id int, timezone string) profile {
var profiles = profile{}
var createdAtTime time.Time
var genderNumber int // Gender is just a number.
db.QueryRow("SELECT created_at, nnid, mh, avatar_image, avatar_id, gender, region, comment, nnid_visibility, yeah_visibility, reply_visibility, discord, steam, psn, switch_code, twitter, youtube, allow_friend, favorite FROM profiles WHERE user = ?", id).Scan(&createdAtTime, &profiles.NNID, &profiles.MiiHash, &profiles.AvatarImage, &profiles.AvatarID, &genderNumber, &profiles.Region, &profiles.CommentText, &profiles.NNIDVisibility, &profiles.YeahVisibility, &profiles.ReplyVisibility, &profiles.Discord, &profiles.Steam, &profiles.PSN, &profiles.SwitchCode, &profiles.Twitter, &profiles.YouTube, &profiles.AllowFriend, &profiles.FavoritePostID)
profiles.Gender = [6]string{"", "He/him", "She/her", "He/she", "Nonbinary", "They/them"}[genderNumber]
profiles.User = id
profiles.Comment = parseBodyWithLineBreaks(profiles.CommentText, false, true)
profiles.CreatedAt = humanTiming(createdAtTime, timezone)
profiles.CreatedAtUnix = createdAtTime.Unix()
return profiles
}
// Find a community by ID.
func QueryCommunity(id string, canBeRM bool) community {
var communities = community{}
if canBeRM {
db.QueryRow("SELECT id, title, description, icon, banner, is_featured, permissions, rm FROM communities WHERE id = ?", id).Scan(&communities.ID, &communities.Title, &communities.DescriptionText, &communities.Icon, &communities.Banner, &communities.IsFeatured, &communities.Permissions, &communities.RM)
} else {
db.QueryRow("SELECT id, title, description, icon, banner, is_featured, permissions, rm FROM communities WHERE id = ? AND rm = 0", id).Scan(&communities.ID, &communities.Title, &communities.DescriptionText, &communities.Icon, &communities.Banner, &communities.IsFeatured, &communities.Permissions, &communities.RM)
}
communities.Description = parseBodyWithLineBreaks(communities.DescriptionText, false, true)
return communities
}
// Set variables for profile sidebar.
func setupProfileSidebar(user user, currentUser user, profileOnPage string) profileSidebar {
var sidebar profileSidebar
sidebar.Profile = QueryProfile(user.ID, currentUser.Timezone)
sidebar.User = user
sidebar.CurrentUser = currentUser
sidebar.ProfileOnPage = profileOnPage
sidebar.Reasons = settings.ReportReasons
if len(sidebar.User.Theme) > 0 {
sidebar.User.ThemeColors = strings.Split(sidebar.User.Theme, ",")
}
db.QueryRow("SELECT COUNT(*) FROM follows WHERE follow_to = ? AND follow_by = ? LIMIT 1", user.ID, currentUser.ID).Scan(&sidebar.IsFollowing)
var requestTimestamp time.Time
_ = db.QueryRow("SELECT COUNT(*), created_at FROM friend_requests WHERE request_to = ? AND request_by = ? GROUP BY created_at", user.ID, currentUser.ID).Scan(&sidebar.FriendStatus, &requestTimestamp)
if sidebar.FriendStatus > 0 {
sidebar.FriendStatus = 2
sidebar.RequestTime = requestTimestamp.Format("01/02/2006 3:04 PM")
} else {
db.QueryRow("SELECT COUNT(*) FROM friend_requests WHERE request_to = ? AND request_by = ?", currentUser.ID, user.ID).Scan(&sidebar.FriendStatus)
if sidebar.FriendStatus > 0 {
sidebar.FriendStatus = 1
var createdAt time.Time
db.QueryRow("SELECT id, message, created_at FROM friend_requests WHERE request_to = ? AND request_by = ? ORDER BY friend_requests.id DESC", currentUser.ID, user.ID).Scan(&sidebar.Request.ID, &sidebar.Request.Message, &createdAt)
sidebar.Request.CreatedAt = createdAt.Format("01/02/2006 3:04 PM")
} else {
db.QueryRow("SELECT COUNT(*) FROM friendships WHERE (source = ? AND target = ?) OR (source = ? AND target = ?)", user.ID, currentUser.ID, currentUser.ID, user.ID).Scan(&sidebar.FriendStatus)
if sidebar.FriendStatus > 0 {
sidebar.FriendStatus = 3
} else {
sidebar.FriendStatus = 0
if sidebar.Profile.AllowFriend == 1 {
db.QueryRow("SELECT COUNT(*) FROM follows WHERE follow_to = ? AND follow_by = ? LIMIT 1", currentUser.ID, user.ID).Scan(&sidebar.IsFollowingMe)
}
}
}
}
sidebar.User.Blocked = checkIfBlocked(currentUser.ID, user.ID)
sidebar.Profile.FriendCount, sidebar.Profile.FollowingCount, sidebar.Profile.FollowerCount = setupSidebarStatus(user.ID)
var banCount int
db.QueryRow("SELECT COUNT(*) FROM bans WHERE user = ?", user.ID).Scan(&banCount)
if banCount > 0 {
if len(sidebar.User.Role.Organization) > 0 {
sidebar.User.Role.Organization = "Banned<br>" + sidebar.User.Role.Organization
} else {
sidebar.User.Role.Organization = "Banned"
}
}
db.QueryRow("SELECT COUNT(*) FROM posts WHERE created_by = ? AND is_rm = 0", user.ID).Scan(&sidebar.Profile.PostCount)
db.QueryRow("SELECT COUNT(*) FROM comments WHERE created_by = ? AND is_rm = 0", user.ID).Scan(&sidebar.Profile.CommentCount)
db.QueryRow("SELECT COUNT(*) FROM yeahs WHERE yeah_by = ?", user.ID).Scan(&sidebar.Profile.YeahCount)
db.QueryRow("SELECT image FROM posts WHERE id = ?", sidebar.Profile.FavoritePostID).Scan(&sidebar.Profile.FavoritePostImage)
favorite_rows, err := db.Query("SELECT communities.id, title, icon FROM communities LEFT JOIN community_favorites ON communities.id = community WHERE favorite_by = ? AND rm = 0 ORDER BY community_favorites.id DESC LIMIT 10", user.ID)
if err != nil {
fmt.Println("error while getting favorite communities")
fmt.Println(err.Error())
return sidebar
}
var favorites []community
for favorite_rows.Next() {
var row = community{}
err := favorite_rows.Scan(&row.ID, &row.Title, &row.Icon)
if err != nil {
fmt.Println("error while scanning favorite communities")
fmt.Println(err.Error())
}
favorites = append(favorites, row)
}
favorite_rows.Close()
sidebar.FavoriteCommunities = favorites
return sidebar
}
// Set friend/following/follower counts for sidebars.
func setupSidebarStatus(userID int) (int, int, int) {
friendCount, followingCount, followerCount := 0, 0, 0
if userID != 0 {
db.QueryRow("SELECT COUNT(*) FROM friendships WHERE source = ? OR target = ?", userID, userID).Scan(&friendCount)
db.QueryRow("SELECT COUNT(*) FROM follows WHERE follow_by = ?", userID).Scan(&followingCount)
db.QueryRow("SELECT COUNT(*) FROM follows WHERE follow_to = ?", userID).Scan(&followerCount)
}
return friendCount, followingCount, followerCount
}
// Cut a string off at 200 characters if needed, and parse Markdown and later emotes.
func parseBody(body string, cutoff bool, parseMarkdown bool) template.HTML {
// Cut off at 200 characters if cutoff is set.
if cutoff && utf8.RuneCountInString(body) > 203 {
runes := []rune(body) // What is this, fucking RuneScape!?
body = string(runes[0:200]) + "..."
}
// Parse markdown and sanitize HTML.
if parseMarkdown {
body = strings.Replace(body, "<3", "\\<3", -1)
bodyTemp := blackfriday.Run([]byte(body), blackfriday.WithRenderer(renderer))
if len(bodyTemp) >= 7 {
rune2 := []rune(string(bodyTemp))
body = string(rune2[:len(rune2)-1])
} else {
body = string(bodyTemp)
}
}
body = bluemonday.UGCPolicy().Sanitize(body)
// Parse emotes.
matches := emotes.FindAllStringSubmatch(body, settings.EmoteLimit)
for _, match := range matches {
var image sql.NullString
db.QueryRow("SELECT image FROM emotes WHERE name = ?", match[1]).Scan(&image)
if image.Valid {
if len(image.String) > 0 {
body = strings.Replace(body, match[0], "<img title=\""+match[1]+"\" src=\""+image.String+"\">", 1)
} else {
body = strings.Replace(body, match[0], "", 1)
}
}
}
// Return the output.
return template.HTML(body)
}
// Parse a body with parseBody(), and then replace line breaks with <br> elements.
func parseBodyWithLineBreaks(body string, cutoff bool, parseMarkdown bool) template.HTML {
bodyHTML := parseBody(body, cutoff, parseMarkdown)
body = strings.Replace(string(bodyHTML), "\n", "<br>", -1)
return template.HTML(body)
}
// Cut a string off at 200 characters.
func parseCutoff(body template.HTML) template.HTML {
return body
}
// Cut a string off at 15 characters. Used for notifications and the "View _____'s post for this comment" bar thingy at the top of the comments page.
func parsePreview(body string, postType int, isRM bool) string {
if isRM {
body = "deleted"
} else if len(body) == 0 {
body = "empty"
} else if postType == 1 {
body = "handwritten"
} else if utf8.RuneCountInString(body) > 18 {
runes := []rune(body)
body = string(runes[0:15]) + "..."
}
return body
}
// Really minimal function to check if the user viewing the page can give a Yeah to a certain post.
func checkIfCanYeah(currentUser user, createdBy int) bool {
if len(currentUser.Username) == 0 {
return false
}
if createdBy == currentUser.ID {
return false
}
if checkIfEitherBlocked(currentUser.ID, createdBy) {
return false
}
return true
}
// Generate the name of a group chat from an array of user nicknames.
func getGroupName(users []string) string {
groupName := "Group chat with "
if len(users) < 1 {
groupName += "yourself"
} else if len(users) == 1 {
groupName += users[0]
} else if len(users) == 2 {
groupName += users[0] + " and " + users[1]
} else {
for i := 0; i < len(users)-2; i++ {
groupName += users[i] + ", "
}
groupName += users[len(users)-2] + " and " + users[len(users)-1]
}
return groupName
}
// Fetch the site's settings from a config.json file.
func getSettings() config {
var settings config
settingsJSON, err := ioutil.ReadFile("config.json")
if err != nil {
fmt.Println(err.Error())
return settings
}
err = json.Unmarshal(settingsJSON, &settings)
if err != nil {
fmt.Println(err.Error())
}
settings.ReportReasons = append([]reportReason{{
Name: "Spoiler",
Message: "Your post contained spoilers, so it was removed.",
Enabled: false,
BodyRequired: true,
}}, settings.ReportReasons...)
return settings
}
// Generate a login token for autoauth.
func generateLoginToken() string {
const letterBytes = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890"
const (
letterIdxBits = 6
letterIdxMask = 1<<letterIdxBits - 1
letterIdxMax = 63 / letterIdxBits
)
src := rand.NewSource(time.Now().UnixNano())
b := make([]byte, 16)
for i, cache, remain := 15, src.Int63(), letterIdxMax; i >= 0; {
if remain == 0 {
cache, remain = src.Int63(), letterIdxMax
}
if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
b[i] = letterBytes[idx]
i--
}
cache >>= letterIdxBits
remain--
}
return string(b)
}
// Check if a user is blocking another user.
func checkIfBlocked(source int, target int) bool {
var isBlocked bool
err := db.QueryRow("SELECT COUNT(*) FROM blocks WHERE source = ? AND target = ?", source, target).Scan(&isBlocked)
if err != nil {
fmt.Println("error while getting blocks")
fmt.Println(err.Error())
}
return isBlocked
}
// Check if a user is blocking another user, or vice versa.
func checkIfEitherBlocked(source int, target int) bool {
var isBlocked bool
err := db.QueryRow("SELECT COUNT(*) FROM blocks WHERE (source = ? AND target = ?) OR (source = ? AND target = ?)", source, target, target, source).Scan(&isBlocked)
if err != nil {
fmt.Println("error while checking if either ballcoks")
fmt.Println(err.Error())
}
return isBlocked
}
// Render a user's avatar as a Mii URL with an emotion or return it if it's not a Mii.
func getAvatar(avatar string, hasMii bool, feeling int) string {
const url = "https://mii-secure.cdn.nintendo.net/%s_%s_face.png"
if len(avatar) == 0 {
return "/assets/img/anonymous.png"
}
if hasMii {
switch feeling {
case 1, 8:
return fmt.Sprintf(url, avatar, "happy")
case 2, 7:
return fmt.Sprintf(url, avatar, "like")
case 3, 6:
return fmt.Sprintf(url, avatar, "surprised")
case 4:
return fmt.Sprintf(url, avatar, "frustrated")
case 5:
return fmt.Sprintf(url, avatar, "puzzled")
default:
return fmt.Sprintf(url, avatar, "normal")
}
}
return avatar
}
// Get the database values necessary for showing a comment preview.
func getCommentPreview(postID int, currentUser user) comment {
var commentPreview comment
var timestamp time.Time
var editedAt time.Time
var role int
db.QueryRow("SELECT comments.id, created_at, edited_at, feeling, body, post_type, username, nickname, avatar, has_mh, online, hide_online, color, role FROM comments INNER JOIN users ON users.id = created_by WHERE post = ? AND is_rm = 0 AND is_rm_by_admin = 0 AND is_spoiler = 0 AND (users.id NOT IN (SELECT if(source = ?, target, source) FROM blocks WHERE (source = ? AND target = users.id) OR (source = users.id AND target = ?)) OR ? > 0) AND IF(created_by = ?, true, LOWER(body) NOT REGEXP LOWER(?)) ORDER BY comments.id DESC LIMIT 1", postID, currentUser.ID, currentUser.ID, currentUser.ID, currentUser.Level, currentUser.ID, escapeForbiddenKeywords(currentUser.ForbiddenKeywords)).Scan(&commentPreview.ID, &timestamp, &editedAt, &commentPreview.Feeling, &commentPreview.BodyText, &commentPreview.PostType, &commentPreview.CommenterUsername, &commentPreview.CommenterNickname, &commentPreview.CommenterIcon, &commentPreview.CommenterHasMii, &commentPreview.CommenterOnline, &commentPreview.CommenterHideOnline, &commentPreview.CommenterColor, &role)
commentPreview.CommenterIcon = getAvatar(commentPreview.CommenterIcon, commentPreview.CommenterHasMii, commentPreview.Feeling)
if role > 0 {
commentPreview.CommenterRoleImage = getRoleImage(role)
}
commentPreview.CreatedAt = humanTiming(timestamp, currentUser.Timezone)
commentPreview.CreatedAtUnix = timestamp.Unix()
if editedAt.Sub(timestamp).Minutes() > 5 {
commentPreview.EditedAt = humanTiming(editedAt, currentUser.Timezone)
commentPreview.EditedAtUnix = editedAt.Unix()
}
commentPreview.Body = parseBody(commentPreview.BodyText, false, true)
return commentPreview
}
// Check if a string violates a user's forbidden keywords.
func inForbiddenKeywords(text string, userID int) bool {
var forbiddenKeywords string
err := db.QueryRow("SELECT forbidden_keywords FROM users WHERE id = ?", userID).Scan(&forbiddenKeywords)
if err != nil {
fmt.Println("error while getting forbidden keyword")
fmt.Println(err)
}
isMatch, err := regexp.MatchString(escapeForbiddenKeywords(forbiddenKeywords), text)
if err != nil {
fmt.Println("error while dying")
fmt.Println(err)
}
return isMatch
}
// Get the current hostname.
// TODO: this prints my local server's hostname like 127.0.0.1:8003:8003
// but is it really worth fixing
func getHostname(host string) string {
hostname := "http"
if settings.SSL.Enabled {
hostname += "s"
}
hostname += "://" + host
if (settings.Port == ":80" && settings.SSL.Enabled) || (settings.Port == ":443" && !settings.SSL.Enabled) || (settings.Port != ":80" && settings.Port != ":443") {
hostname += settings.Port
}
return hostname
}
// Get the current user's IP.
func getIP(r *http.Request) string {
ForwardedForHeader := r.Header.Get("X-Forwarded-For")
if settings.Proxy && len(ForwardedForHeader) > 0 { // Proxy sites like Cloudflare mask the IP, so grab that from the headers... if it's set in the settings, that is; otherwise, people could fake this and we'd have an impersonation exploit on our hands. (Looking at you, Seth)
//ips := strings.Split(r.Header.Get("X-Forwarded-For"), ", ")
//return ips[0] + settings.Port
return ForwardedForHeader + ":0"
} else {
return r.RemoteAddr
}
}
// Get a poll from an ID.
func getPoll(pollID int, userID int) poll {
var newPoll poll
option_rows, err := db.Query("SELECT options.id, name FROM options WHERE post = ?", pollID)
if err != nil {
fmt.Println("could not get poll")
fmt.Println(err.Error())
return newPoll
}
for option_rows.Next() {
var row = option{}
option_rows.Scan(&row.ID, &row.Name)
user_rows, err := db.Query("SELECT users.id FROM votes LEFT JOIN users ON users.id = user WHERE poll = ? AND option_id = ?", pollID, row.ID)
if err != nil {
fmt.Println("could not die")
fmt.Println(err.Error())
return newPoll
}
for user_rows.Next() { // OPTIMIZE THIS!!!
var currentID int
user_rows.Scan(&currentID)
if currentID == userID {
row.Selected = true
newPoll.Selected = true
}
row.Votes = row.Votes + 1
}
user_rows.Close()
newPoll.Options = append(newPoll.Options, row)
}
option_rows.Close()
for _, row := range newPoll.Options {
newPoll.Votes = newPoll.Votes + row.Votes
}
for i, row := range newPoll.Options {
if row.Votes > 0 {
newPoll.Options[i].Percentage = math.Round(row.Votes / newPoll.Votes * 100)
} else {
row.Percentage = 0
}
}
newPoll.ID = pollID
return newPoll
}
// Get the image of a role from its ID.
func getRoleImage(roleID int) string {
var image string
db.QueryRow("SELECT image FROM roles WHERE id = ?", roleID).Scan(&image)
return image
}
// Get the image and organization of a role from its ID.
func getRoleImageAndOrganization(roleID int) (string, string) {
var image string
var organization string
err := db.QueryRow("SELECT image, organization FROM roles WHERE id = ?", roleID).Scan(&image, &organization)
if err != nil && err != sql.ErrNoRows {
fmt.Println("role lookup failed: " + err.Error())
}
return image, organization
}
// Get a user's timezone from their IP.
func getTimezone(ip string) string {
if isGeoIPEnabled == false {
return settings.DefaultTimezone
}
parsedHost, _, _ := net.SplitHostPort(ip)
parsedIP := net.ParseIP(parsedHost)
if parsedIP == nil {
fmt.Println("parsedIP was nil")
return settings.DefaultTimezone
}
city, err := geoip.City(parsedIP)
if err != nil {
fmt.Println("no city")
fmt.Println(err.Error())
return settings.DefaultTimezone
}
if len(city.Location.TimeZone) > 0 {
return city.Location.TimeZone
} else {
return settings.DefaultTimezone
}
}
// Write to a websocket.
func writeWs(session *wsSession, client *websocket.Conn, message wsMessage) error {
session.Send <- message
return nil
}

14
views/activity.html Normal file
View File

@ -0,0 +1,14 @@
{{if .Posts}}
<div class="list post-list js-post-list" data-next-page-url="?offset={{.Offset}}">
{{$user_id := .CurrentUser.ID}}
{{range $post := .Posts}}
{{template "render_post.html" $post}}
{{end}}
</div>
{{else}}
{{if eq .Offset 20}}
<div class="post-list-outline no-content no-post-content">
<p>the timeline empty....<br>spend more time on your Social Links.</p>
</div>
{{end}}
{{end}}

123
views/activity_loading.html Normal file
View File

@ -0,0 +1,123 @@
{{if .Pjax}}
{{template "header.html" .}}
{{else}}
<title>{{.Title}}</title>
{{end}}
<div id="main-body">
{{template "general_sidebar.html" .}}
<div class="main-column">
<div class="headline">
<h2 class="headline-text">
<span class="symbol activity-headline">timeline</span>
</h2>
<form class="search" action="/users">
<input type="text" name="query" title="Search users" placeholder="Search users" minlength="1" maxlength="32">
<input type="submit" value="q" title="Search">
</form>
</div>
<div class="post-form none">
<form id="post-form" method="post" action="/communities/0/posts"{{if not .Repost.ID}} class="folded"{{end}} data-post-subtype="default" name="test-post-default-form">
<input type="hidden" name="csrfmiddlewaretoken" value="{{.CurrentUser.CSRFToken}}">
<input type="hidden" name="community" value="0">
{{if .Repost.ID}}<input type="hidden" name="repost" value="{{.Repost.ID}}">
<p class="repost-notice">You are reposting <a class="topic-title" href="/posts/{{.Repost.ID}}" target="_blank">{{.Repost.Nickname}}'s post ({{.Repost.Text}})</a> along with this submission.</p>{{end}}
<div class="feeling-selector js-feeling-selector"><label class="symbol feeling-button feeling-button-normal checked"><input type="radio" name="feeling_id" value="0" checked><span class="symbol-label">normal</span></label><label class="symbol feeling-button feeling-button-happy"><input type="radio" name="feeling_id" value="1"><span class="symbol-label">happy</span></label><label class="symbol feeling-button feeling-button-like"><input type="radio" name="feeling_id" value="2"><span class="symbol-label">like</span></label><label class="symbol feeling-button feeling-button-surprised"><input type="radio" name="feeling_id" value="3"><span class="symbol-label">surprised</span></label><label class="symbol feeling-button feeling-button-frustrated"><input type="radio" name="feeling_id" value="4"><span class="symbol-label">frustrated</span></label><label class="symbol feeling-button feeling-button-puzzled"><input type="radio" name="feeling_id" value="5"><span class="symbol-label">puzzled</span></label></div>
<div class="textarea-with-menu active-text">
<menu class="textarea-menu">
<li><label class="textarea-menu-text"><input type="radio" name="post_type" value="0"></label></li>
<li><label class="textarea-menu-memo"><input type="radio" name="post_type" value="1"></label></li>
<li><label class="textarea-menu-poll"><input type="radio" name="post_type" value="2"></label></li>
<span class="character-count">2000</span>
</menu>
<div class="textarea-container">
<textarea name="body" class="textarea-text textarea" maxlength="2000" placeholder="Share your thoughts in a post to the Activity Feed." data-open-folded-form data-required></textarea>
</div>
<div class="textarea-memo none">
<div id="memo-drawboard-page" class="none">
<div class="window-body">
<div class="memo-buttons">
<button type="button" class="artwork-clear"></button>
<button type="button" class="artwork-undo"></button>
<button type="button" class="artwork-pencil small selected"></button>
<button type="button" class="artwork-eraser small"></button>
<button type="button" class="artwork-fill"></button>
<input type="text" class="artwork-color">
<button type="button" class="artwork-zoom"></button>
</div>
<div class="memo-canvas">
<canvas id="artwork-canvas" zoom="2"></canvas>
<canvas id="artwork-canvas-undo"></canvas>
<canvas id="artwork-canvas-redo"></canvas>
<input type="hidden" name="painting">
</div>
<div class="form-buttons">
<input class="olv-modal-close-button black-button memo-finish-btn" type="button" value="Save">
<button type="button" class="artwork-lock none"></button>
</div>
</div>
</div>
</div>
<div class="textarea-poll none">
<button type="button" class="delete none" option="option-a"></button><input type="text" class="url-form option" name="option-a" placeholder="Option A" maxlength="64" data-required>
<button type="button" class="delete none" option="option-b"></button><input type="text" class="url-form option" name="option-b" placeholder="Option B" maxlength="64" data-required>
<button type="button" class="add-option symbol">Add Option</button>
</div>
</div>
<label class="file-button-container">
<span class="input-label">Attachment <span>Images, audio and videos are allowed.
{{if .MaxUploadSize}}Maximum upload size: {{.MaxUploadSize}}{{end}}
</span></span>
<span class="button file-upload-button">Upload</span>
<input accept="image/*, audio/*, video/*" type="file" class="file-button none">
<input type="hidden" name="image">
<input type="hidden" name="attachment_type">
<div class="screenshot-container still-image preview-container" style="display: none;">
<img class="preview-image none">
<video class="preview-video none" controls></video>
<audio class="preview-audio none" controls></audio>
</div>
<script src="/assets/js/upload.js"></script>
</label>
<div class="post-form-footer-options">
<div class="post-form-footer-option-inner post-form-spoiler js-post-form-spoiler test-post-form-spoiler">
<label class="spoiler-button symbol"><input type="checkbox" id="is_spoiler" name="is_spoiler" value="1">Spoilers</label>
</div>
</div>
<div class="post-form-privacy">
<p>Who should be able to see this post?</p>
<select class="post-form-privacy-select" name="privacy">
<option value="0"{{if eq .CurrentUser.DefaultPrivacy 0}} selected{{end}}>Everyone</option>
<option value="1"{{if eq .CurrentUser.DefaultPrivacy 1}} selected{{end}}>Friends, Following and Followers</option>
<option value="2"{{if eq .CurrentUser.DefaultPrivacy 2}} selected{{end}}>Friends and Following</option>
<option value="3"{{if eq .CurrentUser.DefaultPrivacy 3}} selected{{end}}>Friends and Followers</option>
<option value="4"{{if eq .CurrentUser.DefaultPrivacy 4}} selected{{end}}>Friends Only</option>
<option value="5"{{if eq .CurrentUser.DefaultPrivacy 5}} selected{{end}}>Followers and Following</option>
<option value="6"{{if eq .CurrentUser.DefaultPrivacy 6}} selected{{end}}>Followers Only</option>
<option value="7"{{if eq .CurrentUser.DefaultPrivacy 7}} selected{{end}}>Following Only</option>
<option value="8"{{if eq .CurrentUser.DefaultPrivacy 8}} selected{{end}}>Admins Only</option>
<option value="9"{{if eq .CurrentUser.DefaultPrivacy 9}} selected{{end}}>Only Me</option>
</select>
</div>
<div class="form-buttons">
<input type="submit" class="black-button post-button disabled" value="Send" data-community-id="{{.Community.ID}}" data-post-content-type="text" data-post-with-screenshot="nodata" disabled>
</div>
</form>
</div>
<div id="js-main">
<div class="activity-feed content-loading-window">
<span class="loading-spinner"></span>
<div><p class="tleft"><span>its all going down....</span></p></div>
<div class="activity-feed content-load-error-window none">
<div>
<p>couldnt load the page sorry!!</p><br>
<img src="https://booru.soy/_images/d305f80b6f0b5df18492ab7aa039f899/1229%20-%20SoyBooru.png">
<div class="buttons-content"><a href="/activity" class="button">Reload</a></div>
</div>
</div>
</div>
</div>
</div>
</div>
{{if .Pjax}}
{{template "footer.html"}}
{{end}}

View File

@ -0,0 +1,48 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<style>
body {
background-color: #20262e;
color: white;
font-family: sans-serif;
}
a {
color: #3f8aff;
}
h1 {
margin: 0;
font-size: 128px;
}
img {
width: 50px;
}
</style>
<title></title>
</head>
<body>
{{if eq .Offset 0}}<p>arian was rushed to make this by the Indigo staff team, sorry it sucks so much</p>{{end}}
<form>
number to start at: <input type="number" name="offset" placeholder="offset" value="{{.Offset}}">
type: <select name="type">
<option value=""></option>
<option value="0"{{if eq .Type "0"}} selected{{ end }}>post delete</option>
<option value="1"{{if eq .Type "1"}} selected{{ end }}>comment delete</option>
<option value="2"{{if eq .Type "2"}} selected{{ end }}>ban</option>
<option value="3"{{if eq .Type "3"}} selected{{ end }}>unban</option>
</select>
username: <input type="text" name="username" placeholder="admin username" value="{{.User}}">
<button>go</button>
<input type="hidden" name="offset_time" value="{{.OffsetTime}}">
</form>
<ul>
{{range $entry := .AuditLogEntries}}
<li>
<a href="/users/{{$entry.CreatorUsername}}" title="{{$entry.CreatorNickname}}"><img src="{{$entry.CreatorFinalAva}}" title="{{$entry.CreatorNickname}}" alt="{{$entry.CreatorNickname}}"></a> <a href="/users/{{$entry.CreatorUsername}}">{{$entry.CreatorNickname}}</a> did <b><a href="{{$entry.TypeURI}}">{{$entry.TypeText}}</b> {{if or (not (eq $entry.Type 4)) (not (eq $entry.CreatorFinalAva $entry.TargetUserAvatar))}}<img src="{{$entry.TargetUserAvatar}}">{{end}}{{$entry.PostSummary}}</a>
| #{{$entry.ID}} {{$entry.CreatedAt}}
</li>
{{end}}
</ul>
</body>
</html>

View File

@ -0,0 +1,51 @@
{{if eq .Offset 25}}
{{if .Pjax}}
{{template "header.html" .}}
{{else}}
<title>{{.Title}}</title>
{{end}}
<div id="main-body">
<div id="sidebar">
<menu id="admin-menu">
<li id="admin-menu-list">
<ul>
<li id="admin-menu-dashboard" class="selected"><a href="/admin" class="symbol"><span>Dashboard</span></a></li>
{{if le .Admin.Manage.MinimumLevel .CurrentUser.Level}}<li id="admin-menu-manage"><a href="/admin/manage" class="symbol"><span>Manage</span></a></li>{{end}}
{{if le .Admin.Settings.MinimumLevel .CurrentUser.Level}}<li id="admin-menu-settings"><a href="/admin/settings" class="symbol"><span>Settings</span></a></li>{{end}}
</ul>
</li>
</menu>
</div>
<div class="main-column">
<div class="admin-dashboard">
<div id="postsz">
<div class="body-content" id="community-post-list">
{{end}}
<div class="list post-list js-post-list"{{if .Reports}} data-next-page-url="?offset={{.Offset}}&offset_time={{.OffsetTime}}"{{end}}>
{{if .Reports}}
{{range $report := .Reports}}
<div id="{{$report.ID}}" class="report post post-list-outline">
<p class="user-name">Reported by: <a href="/users/{{$report.ByUsername}}"{{if $report.ByColor}} style="color:{{$report.ByColor}}"{{end}}>{{$report.ByNickname}}</a></p>
<code class="report-message">{{$report.Message}}</code>
{{template "render_post.html" $report.Post}}
<div class="form-buttons">
<button class="report-action-button gray-button" type="button" data-action="/reports/{{$report.ID}}/ignore">Ignore</button><button class="report-action-button black-button" type="button" data-action="/{{if eq $report.Type 1}}comment{{else if eq $report.Type 2}}user{{else}}post{{end}}s/{{$report.Post.ID}}/delete">Delete</button>
</div>
</div>
{{end}}
{{else if eq .Offset 25}}
<div class="no-content no-post-content post-list-outline">
<p>No posts have been reported yet.</p>
</div>
{{end}}
</div>
{{if eq .Offset 25}}
</div>
</div>
</div>
</div>
</div>
{{if .Pjax}}
{{template "footer.html"}}
{{end}}
{{end}}

57
views/admin/manage.html Normal file
View File

@ -0,0 +1,57 @@
{{if .Pjax}}
{{template "header.html" .}}
{{else}}
<title>{{.Title}} - Indigo</title>
{{end}}
<div id="main-body">
<div id="sidebar">
<menu id="admin-menu">
<li id="admin-menu-list">
<ul>
<li id="admin-menu-dashboard"><a href="/admin" class="symbol"><span>Dashboard</span></a></li>
<li id="admin-menu-manage" class="selected"><a href="/admin/manage" class="symbol"><span>Manage</span></a></li>
{{if le .Admin.Settings.MinimumLevel .CurrentUser.Level}}<li id="admin-menu-settings"><a href="/admin/settings" class="symbol"><span>Settings</span></a></li>{{end}}
</ul>
</li>
</menu>
</div>
<div class="main-column">
<div class="post-list-outline">
<h2 class="label">Admin Manager</h2>
<p style="margin:20px 10px 0px">Pip told me I shouldn't actually work on this until I have the actual essential features for Indigo done, so this is all you're getting for now. Sorry.</p>
<form class="setting-form" method="post" action="/admin/manage/bantemp">
<input type="hidden" name="csrfmiddlewaretoken" value="{{.CurrentUser.CSRFToken}}">
<label class="note"><p><a href="/admin/audit_log">Click to view audit logs.</a></p></label>
<p class="settings-label">Ban User</p>
<input type="text" name="username" placeholder="Username">
<p><label class="note">Ban entire IP range: <input type="range" name="cidr" min="0" max="2" step="1"></label></p>
<label class="note">Length:
<select name="length">
<option value="1">1 day</option>
<option value="2">2 days</option>
<option value="3">3 days</option>
<option value="4">4 days</option>
<option value="5">5 days</option>
<option value="6">6 days</option>
<option value="7">1 week</option>
<option value="14">2 weeks</option>
<option value="28">4 weeks</option>
<option value="90">90 days</option>
<option value="365">1 year</option>
<option value="253383">Life</option>
</select>
</label><br>
<button class="black-button" type="submit">Do it</button>
</form><br>
<form class="setting-form" method="post" action="/admin/manage/unbantemp">
<input type="hidden" name="csrfmiddlewaretoken" value="{{.CurrentUser.CSRFToken}}">
<p class="settings-label">Unban User</p>
<input type="text" name="username" placeholder="Username"><br>
<button class="black-button" type="submit">Do it</button>
</form>
</div>
</div>
</div>
{{if .Pjax}}
{{template "footer.html"}}
{{end}}

165
views/admin/settings.html Normal file
View File

@ -0,0 +1,165 @@
{{if .Pjax}}
{{template "header.html" .}}
{{else}}
<title>{{.Title}}</title>
{{end}}
<div id="main-body">
<div id="sidebar">
<menu id="admin-menu">
<li id="admin-menu-list">
<ul>
<li id="admin-menu-dashboard"><a href="/admin" class="symbol"><span>Dashboard</span></a></li>
{{if le .Admin.Manage.MinimumLevel .CurrentUser.Level}}<li id="admin-menu-manage"><a href="/admin/manage" class="symbol"><span>Manage</span></a></li>{{end}}
<li id="admin-menu-settings" class="selected"><a href="/admin/settings" class="symbol"><span>Settings</span></a></li>
</ul>
</li>
</menu>
</div>
<div class="main-column">
<div class="post-list-outline">
<h2 class="label">Admin Settings</h2>
<form class="setting-form" method="post">
<input type="hidden" name="csrfmiddlewaretoken" value="{{.CurrentUser.CSRFToken}}">
<ul class="settings-list admin-settings-list">
<p class="settings-label">Here you can edit various global Indigo settings.<br>Any changes you make will impact the entire website. If you don't know what any of this is, LEAVE THIS PAGE NOW.</p>
<li>
<p class="settings-label">Image Host</p>
<p class="note">Hosting Provider</p>
<div class="select-content">
<div class="select-button">
<select name="imagehost_provider">
<option value="local"{{if eq .Settings.ImageHost.Provider "local"}} selected{{end}}>Local (not yet implemented)</option>
<option value="cloudinary"{{if eq .Settings.ImageHost.Provider "cloudinary"}} selected{{end}}>Cloudinary</option>
</select>
</div>
</div>
<div class="js-not-local{{if eq .Settings.ImageHost.Provider "local"}} none{{end}}">
<p class="note js-cloud-name">{{if eq .Settings.ImageHost.Provider "cloudinary"}}Cloud N{{else}}Usern{{end}}ame</p>
<div class="center center-input">
<input type="text" name="imagehost_username" placeholder="{{if eq .Settings.ImageHost.Provider "cloudinary"}}Cloud N{{else}}Usern{{end}}ame" value="{{.Settings.ImageHost.Username}}">
</div>
<p class="note js-upload-preset">{{if eq .Settings.ImageHost.Provider "cloudinary"}}Upload Preset{{else}}Password{{end}}</p>
<div class="center center-input">
<input type="text" name="imagehost_uploadpreset" placeholder="{{if eq .Settings.ImageHost.Provider "cloudinary"}}Upload Preset{{else}}Password{{end}}" value="{{.Settings.ImageHost.UploadPreset}}">
</div>
<p class="note">API Endpoint</p>
<div class="center center-input">
<input type="text" name="imagehost_apiendpoint" placeholder="API Endpoint" value="{{.Settings.ImageHost.APIEndpoint}}">
</div>
</div>
<p class="note">Max Upload Size (shown to the client, not enforced)</p>
<div class="center center-input">
<input type="text" name="imagehost_maxuploadsize" placeholder="Max Upload Size" value="{{.Settings.ImageHost.MaxUploadSize}}">
</div>
</li>
<li>
<p class="settings-label">ReCAPTCHA</p>
<label class="note">Enabled: <input type="checkbox" name="recaptcha_enabled" value="1"{{if .Settings.ReCAPTCHA.Enabled}} checked{{end}}></label>
<div class="js-recaptcha-enabled{{if not .Settings.ReCAPTCHA.Enabled}} none{{end}}">
<p class="note">Site Key</p>
<div class="center center-input">
<input type="text" name="recaptcha_sitekey" placeholder="Site Key" value="{{.Settings.ReCAPTCHA.SiteKey}}">
</div>
<p class="note">Secret Key (KEEP SECRET!!!!!!!!!!!!!!!!!!!!)</p>
<div class="center center-input">
<input type="text" name="recaptcha_secretkey" placeholder="Secret Key" value="{{.Settings.ReCAPTCHA.SecretKey}}">
</div>
</div>
</li>
<li>
<p class="settings-label">Webhooks</p>
<label class="note">Enabled: <input type="checkbox" name="webhooks_enabled" value="1"{{if .Settings.Webhooks.Enabled}} checked{{end}}></label>
<div class="js-webhooks-enabled{{if not .Settings.Webhooks.Enabled}} none{{end}}">
<p class="note">Report Webhook</p>
<div class="center center-input">
<input type="text" name="webhooks_reports" placeholder="Report Webhook" value="{{.Settings.Webhooks.Reports}}">
</div>
<p class="note">Signup Webhook</p>
<div class="center center-input">
<input type="text" name="webhooks_signups" placeholder="Signup Webhook" value="{{.Settings.Webhooks.Signups}}">
</div>
<p class="note">Login Webhook</p>
<div class="center center-input">
<input type="text" name="webhooks_logins" placeholder="Login Webhook" value="{{.Settings.Webhooks.Logins}}">
</div>
</div>
</li>
<li>
<p class="settings-label">Email (SMTP)</p>
<label class="note">Enabled: <input type="checkbox" name="smtp_enabled" value="1"{{if .Settings.SMTP.Enabled}} checked{{end}}></label>
<div class="js-smtp-enabled{{if not .Settings.SMTP.Enabled}} none{{end}}">
<p class="note">Hostname</p>
<div class="center center-input">
<input type="text" name="smtp_hostname" placeholder="SMTP Hostname" value="{{.Settings.SMTP.Hostname}}">
</div>
<p class="note">Port</p>
<div class="center center-input">
<input type="text" name="smtp_port" placeholder="SMTP Port" value="{{.Settings.SMTP.Port}}">
</div>
<p class="note">Email Address</p>
<div class="center center-input">
<input type="text" name="smtp_email" placeholder="Email Address" value="{{.Settings.SMTP.Email}}">
</div>
<p class="note">Password</p>
<div class="center center-input">
<input type="text" name="smtp_password" placeholder="Password" value="{{.Settings.SMTP.Password}}">
</div>
</div>
</li>
<!-- insert report reason editor here -->
<li>
<p class="settings-label">Is the site being hosted through a DNS proxy? (e.g. Cloudflare)</p>
<div class="select-content">
<div class="select-button">
<select name="proxy">
<option value="1"{{if eq .Settings.Proxy true}} selected{{end}}>Yes</option>
<option value="0"{{if eq .Settings.Proxy false}} selected{{end}}>No</option>
</select>
</div>
</div>
</li>
<li>
<p class="settings-label">Should users be forced to log in to see the site?</p>
<div class="select-content">
<div class="select-button">
<select name="forcelogins">
<option value="1"{{if eq .Settings.ForceLogins true}} selected{{end}}>Yes</option>
<option value="0"{{if eq .Settings.ForceLogins false}} selected{{end}}>No</option>
</select>
</div>
</div>
</li>
<li>
<p class="settings-label">Should users be allowed to sign up?</p>
<div class="select-content">
<div class="select-button">
<select name="allowsignups">
<option value="1"{{if eq .Settings.AllowSignups true}} selected{{end}}>Yes</option>
<option value="0"{{if eq .Settings.AllowSignups false}} selected{{end}}>No</option>
</select>
</div>
</div>
</li>
<li>
<p class="settings-label">What should the default timezone be?</p>
<div class="center center-input">
<input type="text" name="defaulttimezone" placeholder="Default Timezone" value="{{.Settings.DefaultTimezone}}">
</div>
</li>
<li>
<p class="settings-label">Emote Limit</p>
<div class="center center-input">
<input type="number" name="emotelimit" value="{{.Settings.EmoteLimit}}">
</div>
</li>
<div class="form-buttons">
<input type="submit" class="black-button apply-button" value="Save Settings">
</div>
</ul>
</form>
</div>
</div>
</div>
{{if .Pjax}}
{{template "footer.html"}}
{{end}}

8
views/all_comments.html Normal file
View File

@ -0,0 +1,8 @@
<ul class="list reply-list test-reply-list">
{{range $comment := .PinnedComments}}
{{template "render_comment.html" $comment}}
{{end}}
{{range $comment := .Comments}}
{{template "render_comment.html" $comment}}
{{end}}
</ul>

37
views/auth/ban.html Normal file
View File

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>BANNED LOL!</title>
<meta http-equiv="content-style-type" content="text/css">
<meta http-equiv="content-script-type" content="text/javascript">
<meta name="format-detection" content="telephone=no">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="apple-mobile-web-app-title" content="Indigo">
<meta name="description" content="go use truth social or some shit">
<meta property="og:locale" content="en_US">
<meta property="og:title" content="{{.Title}}">
<meta property="og:type" content="article">
<meta property="og:image" content="/assets/img/favicon.png">
<meta property="og:description" content="go use truth social or some shit">
<meta property="og:site_name" content="sop.epic">
<meta property="article:published_time" content="None">
<link rel="shortcut icon" type="image/png" href="/assets/img/favicon.png">
<link rel="stylesheet" type="text/css" href="/assets/css/offdevice.css">
<link id="darkness" {{if .CurrentUser.LightMode}}disabled{{end}} rel="stylesheet" type="text/css" href="/assets/css/dark.css">
</head>
<body class="simple-wrapper simple-wrapper-content">
<div id="wrapper" style="margin-top:0">
<div id="main-body">
<div class="warning-content warning-content-restricted track-error" data-track-error="restricted">
<div>
<p>BANNED LOL!!!!!!!!!!!!!</p>
<p>Ban expiration date: <strong>{{.Length}}</strong>{{ if .LengthForever }} okay so basically forever{{ end }}</p>
</div>
</div>
<img src="/assets/img/restricted.png" width="800" height="600">
</div>
</div>
</body>

42
views/auth/login.html Normal file
View File

@ -0,0 +1,42 @@
{{if .Pjax}}
{{template "header.html" .}}
<meta property="og:description" content="welcome back!!">
{{else}}
<title>{{.Title}}</title>
{{end}}
<div id="main-body" class="guest">
<div class="main-column center">
<div class="post-list-outline login-page">
<form method="post">
{{.CSRFField}}
<img src="/assets/img/menu-logo.png">
<p class="lh">Log In</p>
<p>
{{if not .ForceLogins}}
welcome back!!
{{else}}
<img src="https://res.cloudinary.com/indigolang/image/upload/v1540207716/xlg6ms1uq661a5qp9gp1.png" style="width:100%">
{{end}}
</p>
<h3 class="label"><label>User ID: <input type="text" class="auth-input" name="username" maxlength="32" placeholder="User ID"></label></h3>
<h3 class="label"><label>Password: <input type="password" class="auth-input" name="password" maxlength="32" placeholder="Password"></label></h3>
<p class="red" style="margin-bottom:6px">{{if eq .FormError "1"}}The username/password combination you entered is not correct.{{end}}</p>
<button type="submit" class="button">Sign In</button>
<div class="ll">
</div>
{{if .AllowSignups}}
<div class="ll">
<p>If you don't have an account, <a href="/signup">you can sign up here.</a></p>
</div>
{{else}}
<div class="ll">
<p>If you don't have an account, <a href="https://www.youtube.com/watch?v=b0T5Qj6DRTk">you can sign up here!!</a></p>
</div>
{{end}}
</form>
</div>
</div>
</div>
{{if .Pjax}}
{{template "footer.html"}}
{{end}}

57
views/auth/reset.html Normal file
View File

@ -0,0 +1,57 @@
{{if .Pjax}}
{{template "header.html" .}}
<meta property="og:description" content="Request a password reset here.">
{{else}}
<title>{{.Title}} - Indigo</title>
{{end}}
<div id="main-body" class="guest">
<div class="main-column center">
<div class="post-list-outline login-page">
<form method="post">
{{.CSRFField}}
<img src="/assets/img/menu-logo.png" alt="Indigo">
<p class="lh">Reset Password{{if .Username}} for {{.Username}}{{end}}{{if .Error}} Error{{end}}</p>
{{if eq .Action "request"}}
<p>Forgot your password or accidentally gave it to someone who shouldn't have it? Don't worry, we've got you covered. Just type your email here and we'll send you a link to change it!</p>
<h3 class="label">
<label>
Email address: <input type="text" class="auth-input" name="email" maxlength="255" placeholder="indigo@pf2m.com">
</label>
</h3>
<button type="submit" class="button">Send</button>
{{else if eq .Action "sent"}}
<p>Success! The email was sent to <strong>{{.Email}}</strong>.</p>
<p>Note that the message may be in your "Junk" or "Spam" boxes, so if you can't find it check there. The message may also take a while to send.</p>
<p>Until then, you can <a href="/">click here to continue browsing the website if you want.</a></p>
{{else if eq .Action "reset"}}
<p>Hooray, you got the email! You're almost done now. Just enter in your new password twice (to make sure you don't misspell it) and hit that nice little button below, it's called the "reset button".</p>
<h3 class="label">
<label>
Password: <input type="password" class="auth-input" name="password" maxlength="255" placeholder="hunter2">
</label>
</h3>
<h3 class="label">
<label>
Confirm Password: <input type="password" class="auth-input" name="confirm" maxlength="255" placeholder="hunter2">
</label>
</h3>
{{if .Error}}<p class="red" style="margin-bottom:6px">{{.Error}}</p>{{end}}
<button type="submit" class="button">Reset</button>
{{else if eq .Action "success"}}
<p>The reset operation was successful! You can now <a href="/login">log into your account</a> with your new password.</p>
<p>Thanks for using Indigo!</p>
{{else if eq .Action "error"}}
<p>{{.Error}}</p>
{{else if eq .Action "disabled"}}
<p>Password resets are not enabled on this instance of Indigo.</p>
<p>To remedy this, tell the owner of the service to set up an SMTP server and specify it in their Indigo configuration.</p>
<p>You can learn how to do this <a href="https://github.com/PF2M/Indigo/wiki">on the Indigo GitHub's Wiki page.</a></p>
{{end}}
<br>
</form>
</div>
</div>
</div>
{{if .Pjax}}
{{template "footer.html"}}
{{end}}

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<body>
<img src="{{.Hostname}}/assets/img/menu-logo.png">
<br>A password reset request has been made for your account.
<br>If you initiated this request, go here: <a href="{{.Hostname}}/reset?token={{.Token}}">{{.Hostname}}/reset?token={{.Token}}</a>
<br>Otherwise, you can probably ignore this email, as these kinds of requests can be sent by anyone.
</body>
</html>

49
views/auth/signup.html Normal file
View File

@ -0,0 +1,49 @@
{{if .Pjax}}
{{template "header.html" .}}
<meta property="og:description" content="sign up BITCH">
{{else}}
<title>{{.Title}}</title>
{{end}}
<div id="main-body" class="guest">
<div class="main-column center">
<div class="post-list-outline login-page">
<form method="post">
<input type="hidden" name="csrfmiddlewaretoken" value="{{.CurrentUser.CSRFToken}}">
<img src="/assets/img/menu-logo.png">
<p class="lh">sign up BITCH</p>
<p>creating an sop.epic account lets you interact with everybody else with an account!</p><br>
<p><img src="/assets/img/bocchi.gif" width="320" height="240"><br>
<a href="/help/rules">please read the TOS thanks</a></p>
<h3 class="label"><label><span class="red">*</span> User ID: <input type="text" class="auth-input" name="username" maxlength="32" minlength="4" placeholder="ID"></label></h3>
<h3 class="label"><label><span class="red">*</span> Nickname: <input type="text" class="auth-input" name="nickname" maxlength="32" placeholder="Nickname"></label></h3>
<label class="file-button-container">
<span class="input-label" style="color:black;">Avatar Image <span>PNG, JPEG and GIF are allowed.</span></span>
<span class="button file-upload-button for-avatar">Upload</span>
<input accept="image/*" type="file" class="file-button none">
<input type="hidden" name="image">
<div class="screenshot-container still-image preview-container">
<img class="preview-image">
</div>
<script src="/assets/js/upload.js"></script>
</label>
<h3 class="label"><label>Email address: <input type="email" class="auth-input" name="email" maxlength="255" placeholder="Email (optional)"></label></h3>
<h3 class="label"><label><span class="red">*</span> Password: <input type="password" class="auth-input" name="password" maxlength="32" placeholder="Password"></label></h3>
<h3 class="label"><label><span class="red">*</span> Confirm Password: <input type="password" class="auth-input" name="confirm" maxlength="32" placeholder="Confirm Password"></label></h3>
{{if .ReCAPTCHA.Enabled}}
<script src="https://www.google.com/recaptcha/api.js"></script>
<div class="g-recaptcha" style="display:inline-block" data-sitekey="{{.ReCAPTCHA.SiteKey}}"></div>
{{end}}
<p class="red" style="margin-bottom:6px"></p>
<button type="submit" class="button">Create account</button>
<div class="ll">
<p>All fields with a red asterisk (<span class="red">*</span>) are required.</p>
<p>You can change all of these fields after you sign up; except for usernames, which can only be changed by admins.</p>
<p>If no email address is associated with your account, you won't be able to reset your password.</p>
</div>
</form>
</div>
</div>
</div>
{{if .Pjax}}
{{template "footer.html"}}
{{end}}

68
views/blocked.html Normal file
View File

@ -0,0 +1,68 @@
{{if .Pjax}}
{{template "header.html" .}}
{{else}}
<title>{{.Title}}</title>
{{end}}
<div id="main-body" class="profile-top">
{{template "general_sidebar.html" .}}
<div class="main-column">
<div class="post-list-outline">
<div class="body-content" id="community-top">
<h2 class="label">Blocked Users</h2>
<ul class="list news-list" {{if .Users}} data-next-page-url="/blocked?offset={{.Offset}}"{{end}}>
{{if .Users}}
{{range $user := .Users}}
<li class="trigger" data-href="/users/{{$user.Username}}">
<a href="/users/{{$user.Username}}" username="{{$user.Username}}" class="icon-container{{if not $user.HideOnline}}{{if $user.Online}} online{{else}} offline{{end}}{{end}}
{{if $user.Role.Image}} official-user"><img src="{{$user.Role.Image}}" class="official-tag">{{else}}">{{end}}
<img src="{{$user.Avatar}}" alt="{{$user.Nickname}}" class="icon">
</a>
<div class="body">
<a href="/users/{{$user.Username}}" class="nick-name"{{if $user.Color}} style="color:{{$user.Color}}"{{end}}>{{$user.Nickname}}</a>
<span class="id-name">{{$user.Username}}</span><br>
<span class="timestamp">{{$user.LastSeen}}</span>
<button class="button received-request-button" type="button" data-user-id="{{$user.ID}}">Unblock</button>
</div>
</li>
<div class="dialog none" data-modal-types="post-unblock" id="{{$user.ID}}">
<div class="dialog-inner">
<div class="window">
<h1 class="window-title">Unblock {{$user.Nickname}}</h1>
<div class="window-body">
<div id="sidebar-profile-body">
<div username="{{$user.Username}}" class="icon-container{{if not $user.HideOnline}}{{if $user.Online}} online{{else}} offline{{end}}{{end}}
{{if $user.Role.Image}} official-user"><img src="{{$user.Role.Image}}" class="official-tag">{{else}}">{{end}}
<a href="/users/{{$user.Username}}">
<img src="{{$user.Avatar}}" alt="{{$user.Nickname}}" class="icon">
</a>
</div>
<a href="/users/{{$user.Username}}" class="nick-name"{{if $user.Color}} style="color:{{$user.Color}}"{{end}}>{{$user.Nickname}}</a>
<p class="id-name">{{$user.Username}}</p>
</div>
<form method="post" data-action="/users/{{$user.Username}}/unblock">
<p class="window-body-content">Unblock this user?</p>
<div class="form-buttons">
<input type="button" class="olv-modal-close-button gray-button" value="No">
<input type="submit" value="Yes" class="post-button black-button">
</div>
</form>
</div>
</div>
</div>
</div>
{{end}}
{{else}}
{{if eq .Offset 25}}
<div class="no-content">
<p>You haven't blocked any users.</p>
</div>
{{end}}
{{end}}
</ul>
</div>
</div>
</div>
</div>
{{if .Pjax}}
{{template "footer.html"}}
{{end}}

242
views/comment.html Normal file
View File

@ -0,0 +1,242 @@
{{if .Pjax}}
{{template "header.html" .}}
<meta property="og:profile:username" content="{{.Comment.CommenterUsername}}">
<meta property="og:description" content="{{if .Comment.Body}}{{.Comment.Body}}{{else}}View {{.Comment.CommenterNickname}}'s comment on Indigo.{{end}}">
{{if .Comment.Image}}<meta property="og:{{if eq .Comment.AttachmentType 1}}audio{{else if eq .Comment.AttachmentType 2}}video{{else}}image{{end}}" content="{{.Comment.Image}}">{{end}}
{{else}}
<title>{{.Title}}</title>
{{end}}
<div id="main-body">
<div class="main-column">
<div class="post-list-outline">
<a class="post-permalink-button info-ticker"{{if not .Post.IsRM}} href="/posts/{{.Comment.PostID}}"{{end}}>
<span class="icon-container"><img src="{{.Post.PosterIcon}}" class="icon"></span>
<span>View <span class="post-user-description">{{.Post.PosterNickname}}'s post ({{.Post.BodyText}})</span> for this comment.</span>
</a>
</div>
<div class="post-list-outline more">
<div id="post-content" class="post reply-permalink-post">
<div id="{{.Comment.ID}}" class="other">
<p class="community-container"><a{{if not .Post.CommunityRM}} href="/communities/{{.Post.CommunityID}}"{{end}}><img src="{{.Post.CommunityIcon}}" class="community-icon">{{.Post.CommunityName}}</a></p>
{{if not .Comment.IsRMByAdmin}}
{{if or (eq .Comment.CreatedBy .CurrentUser.ID) (gt .CurrentUser.Level 0)}}
<div class="edit-buttons-content">
<button type="button" class="symbol button edit-button rm-post-button" data-action="/comments/{{.Comment.ID}}/delete"><span class="symbol-label">Delete</span></button>
{{if and (eq .Comment.CreatedBy .CurrentUser.ID) (not (eq .Comment.PostType 1))}}<button type="button" class="symbol button edit-button edit-post-button"><span class="symbol-label">Edit</span></button>{{end}}
</div>
{{else if .CurrentUser.Username}}
<div class="report-buttons-content" style="float:right"><button type="button" class="report-button" data-modal-open="#report-violation-page" data-screen-name="{{.Comment.CommenterNickname}}" data-support-text="#{{.Comment.ID}}" data-action="/comments/{{.Comment.ID}}/violations" data-is-permalink="1" data-can-report-spoiler="{{if .Comment.IsSpoiler}}0{{else}}1{{end}}" data-url-id="{{.Comment.ID}}" data-track-action="openReportModal">Report Violation</button></div>
{{end}}
{{end}}
<div class="user-content">
<a username="{{.Comment.CommenterUsername}}" href="/users/{{.Comment.CommenterUsername}}" class="icon-container
{{if not .Comment.CommenterHideOnline}}
{{if .Comment.CommenterOnline}}
online
{{else}}
offline
{{end}}
{{end}}
{{if .Comment.CommenterRoleImage}} official-user"><img src="{{.Comment.CommenterRoleImage}}" class="official-tag">{{else}}">{{end}}<img src="{{.Comment.CommenterIcon}}" class="icon">
</a>
<div class="user-name-content">
{{if .Comment.CommenterRoleOrganization}}<p class="user-organization">{{.Comment.CommenterRoleOrganization}}</p>{{end}}
<p class="user-name"><a href="/users/{{.Comment.CommenterUsername}}"{{if .Comment.CommenterColor}} style="color:{{.Comment.CommenterColor}}"{{end}}>{{.Comment.CommenterNickname}}</a></p>
<p class="timestamp-container">
<span class="spoiler-status{{if .Comment.Pinned}} spoiler{{end}}">Pinned ·</span>
{{if .Post.Privacy}}<span class="spoiler-status spoiler">Private ·</span>{{end}}
<span class="spoiler-status{{if .Comment.IsSpoiler}} spoiler{{end}}">Spoilers ·</span>
<span class="timestamp">
<span class="update" time="{{.Comment.CreatedAtUnix}}000">{{.Comment.CreatedAt}}</span>
{{if .Comment.EditedAt}}
(Edited <span class="update" time="{{.Comment.EditedAtUnix}}000">{{.Comment.EditedAt}}</span>)
{{end}}
</span>
</p>
</div>
</div>
<div class="body">
{{if .Comment.IsRMByAdmin}}
<p class="deleted-message">
Deleted by administrator.<br>
Comment ID: #{{.Comment.ID}}
</p>
{{end}}
{{if or (not .Comment.IsRMByAdmin) .Comment.ByMe}}
{{if and (eq .Comment.CreatedBy .CurrentUser.ID) (not (eq .Comment.PostType 1))}}
<div id="post-edit" class="none">
<form data-action="/comments/{{.Comment.ID}}/edit" id="edit-form" method="post">
<div class="feeling-selector js-feeling-selector test-feeling-selector"><label class="symbol feeling-button feeling-button-normal checked"><input type="radio" name="feeling_id" value="0"{{if eq .Comment.Feeling 0}} checked{{end}}><span class="symbol-label">normal</span></label><label class="symbol feeling-button feeling-button-happy"><input type="radio" name="feeling_id" value="1"{{if eq .Comment.Feeling 1}} checked{{end}}><span class="symbol-label">happy</span></label><label class="symbol feeling-button feeling-button-like"><input type="radio" name="feeling_id" value="2"{{if eq .Comment.Feeling 2}} checked{{end}}><span class="symbol-label">like</span></label><label class="symbol feeling-button feeling-button-surprised"><input type="radio" name="feeling_id" value="3"{{if eq .Comment.Feeling 3}} checked{{end}}><span class="symbol-label">surprised</span></label><label class="symbol feeling-button feeling-button-frustrated"><input type="radio" name="feeling_id" value="4"{{if eq .Comment.Feeling 4}} checked{{end}}><span class="symbol-label">frustrated</span></label><label class="symbol feeling-button feeling-button-puzzled"><input type="radio" name="feeling_id" value="5"{{if eq .Comment.Feeling 5}} checked{{end}}><span class="symbol-label">puzzled</span></label></div>
<div class="textarea-container"><textarea name="body" class="textarea-text textarea" maxlength="2000" placeholder="Edit your comment." data-required>{{.Comment.BodyText}}</textarea></div>
<div class="post-form-footer-options">
<label class="spoiler-button symbol"><input id="is_spoiler" name="is_spoiler" type="checkbox" value="1"{{if .Comment.IsSpoiler}} checked{{end}}>Spoilers</label>
</div>
<div class="form-buttons">
<button type="button" class="cancel-button gray-button">Cancel</button>
<button type="submit" class="post-button black-button">Submit</button>
</div>
</form>
</div>
{{else if .CurrentUser.ID}}
<div id="report-violation-page" class="dialog none" data-modal-types="report report-violation" data-is-template="1">
<div class="dialog-inner">
<div class="window">
<h1 class="window-title">report chuddery/h1>
<div class="window-body">
<p class="description">
you are about to report to the owner of SOP.epic on regards of breaking the rules</p>
<form method="post" action="/comments/{{.Comment.ID}}/violations">
<input type="hidden" name="csrfmiddlewaretoken" value="{{.CurrentUser.CSRFToken}}">
<p class="select-button-label">Violation Type: </p>
<select name="type" class="cannot-report-spoiler">
<option selected value>Please make a selection.</option>
{{range $index, $reason := .Reasons}}
{{if $reason.Enabled}}
<option value="{{$index}}"{{if $reason.BodyRequired}} data-body-required="1"{{end}}>{{$reason.Name}}</option>
{{end}}
{{end}}
</select>
<select name="type" class="can-report-spoiler">
<option selected value>Please make a selection.</option>
<option value="spoiler" data-body-required="1" data-track-action="Spoiler">Spoiler</option>
{{range $index, $reason := .Reasons}}
{{if $reason.Enabled}}
<option value="{{$index}}"{{if $reason.BodyRequired}} data-body-required="1"{{end}}>{{$reason.Name}}</option>
{{end}}
{{end}}
</select>
<textarea name="body" class="textarea" maxlength="100" data-placeholder="Enter a reason for the report."></textarea>
<p class="post-id">Reply ID: #{{.Comment.ID}}</p>
<div class="form-buttons">
<input type="button" class="olv-modal-close-button gray-button" value="Cancel">
<input type="submit" class="post-button black-button" value="Submit Report" data-url-id="{{.Comment.ID}}" data-track-action="openReportModal">
</div>
</form>
</div>
</div>
</div>
</div>
{{end}}
<div id="the-post">
{{if eq .Comment.PostType 1}}
<div class="reply-content-memo">
<img class="reply-memo" src="{{.Comment.BodyText}}">
</div>
{{else}}
<div class="reply-content-text">{{.Comment.Body}}</div>
{{end}}
{{if .Comment.Image}}
{{if eq .Comment.AttachmentType 1}}
<div class="screenshot-container">
<audio controls preload="none" src="{{.Comment.Image}}"></audio>
</div>
{{else if eq .Comment.AttachmentType 2}}
<div class="screenshot-container video">
<video controls preload="none" src="{{.Comment.Image}}"></video>
</div>
{{else}}
<div class="screenshot-container still-image">
<img src="{{.Comment.Image}}">
</div>
{{end}}
{{end}}
{{if .Comment.URL}}
{{if eq .Comment.URLType 1}}
<div class="screenshot-container video">
<iframe width="490" height="276" src="https://www.youtube.com/embed/{{.Comment.URL}}" frameborder="0" allowfullscreen="true"></iframe>
</div>
{{else if eq .Comment.URLType 2}}
<div class="screenshot-container video">
<iframe width="490" height="276" src="https://open.spotify.com/embed/track/{{.Comment.URL}}" frameborder="0"></iframe>
</div>
{{else if eq .Comment.URLType 3}}
<div class="screenshot-container video audio">
<iframe class="audio" width="490" height="276" src="https://w.soundcloud.com/player/?url={{.Comment.URL}}&auto_play=false&show_artwork=true&color=8000ff" frameborder="0" allowfullscreen="true"></iframe>
</div>
{{else}}
<p class="url-link">
<a class="link-confirm" href="{{.Comment.URL}}" target="_blank">{{.Comment.URL}}</a>
</p>
{{end}}
{{end}}
{{if .URL}}
{{if eq .URLType 1}}
<div class="screenshot-container video">
<iframe src="https://www.youtube.com/embed/{{.URL}}" width="490" height="276" frameborder="0" allowfullscreen="true"></iframe>
</div>
{{else if eq .URLType 2}}
<div class="screenshot-container video">
<iframe src="https://open.spotify.com/embed/track/{{.URL}}" width="490" height="276" frameborder="0" allow="encrypted-media"></iframe>
</div>
{{else if eq .URLType 3}}
<div class="screenshot-container video audio">
<iframe class="audio" src="https://w.soundcloud.com/player/?url={{.URL}}&auto_play=false&show_artwork=true&color=8000ff" frameborder="0"></iframe>
</div>
{{else}}
<p class="url-link">
<a class="link-confirm" href="{{.URL}}" target="_blank">{{.URL}}</a>
</p>
{{end}}
{{end}}
{{end}}
{{if not .Comment.IsRMByAdmin}}
<div class="post-meta">
<button type="button" {{if not .Comment.CanYeah}}disabled{{end}} class="symbol submit yeah-button
{{if .Comment.Yeahed}} yeah-added{{end}}
" data-feeling="{{.Comment.Feeling}}" data-action="/comments/{{.Comment.ID}}/yeah" data-url-id="{{.Comment.ID}}">
<span class="yeah-button-text">
{{if .Comment.Yeahed}}
{{if eq .Comment.Feeling 6}}
Unepic
{{else if eq .Comment.Feeling 7}}
Unnyeah
{{else if eq .Comment.Feeling 8}}
Unyes
{{else if eq .Comment.Feeling 9}}
olv.portal.miitoo.delete
{{else}}
Unyeah
{{end}}
{{else}}
{{if eq .Comment.Feeling 2}}
Yeah♥
{{else if eq .Comment.Feeling 3}}
Yeah!?
{{else if or (eq .Comment.Feeling 4) (eq .Comment.Feeling 5)}}
Yeah...
{{else if eq .Comment.Feeling 6}}
Epic!
{{else if eq .Comment.Feeling 7}}
Nyeah~♥
{{else if eq .Comment.Feeling 8}}
Yes!
{{else if eq .Comment.Feeling 9}}
olv.portal.miitoo.
{{else}}
Yeah!
{{end}}
{{end}}
</span>
</button>
<div class="yeah symbol"><span class="symbol-label">Yeahs</span><span class="yeah-count">{{.Comment.YeahCount}}</span></div>
</div>
</div>
<div id="yeah-content"{{if not (or (.Yeahs) (.Comment.Yeahed))}} class="none"{{end}}>
<a href="/users/{{.CurrentUser.Username}}" class="post-permalink-feeling-icon visitor"{{if not .Comment.Yeahed}} style="display: none;"{{end}}>
{{if .CurrentUser.Role.Image}}<img src="{{.CurrentUser.Role.Image}}" class="official-tag">{{end}}
<img src="{{.CurrentUser.Avatar}}" class="user-icon">
</a>
{{range $yeah := .Yeahs}}
{{template "yeah_icon.html" $yeah}}
{{end}}
</div>
{{end}}
</div>
</div>
</div>
</div>
</div>
</div>
{{if .Pjax}}
{{template "footer.html"}}
{{end}}

160
views/communities.html Normal file
View File

@ -0,0 +1,160 @@
{{if .AutoPagerize}}
{{if .Pjax}}
{{template "header.html" .}}
{{else}}
<title>{{.Title}} - sop.epic</title>
{{end}}
<div id="main-body" class="community-top">
<a href="https://twitter.com/MastaMiMilla/status/1591521476368556033/photo/1"> <img src="/assets/img/yaoi1.png"> </a> <img src="/assets/img/scoobydio.png"> <img src= "/assets/img/ash.gif" width="160" height="115"> <img src= "/assets/img/garycolemanlostsoul.png"> <img src= "/assets/img/monkeycar.jpg">
<div class="main-column">
{{if not .PopularPosts}}
<form class="search{{if not .Query}} folded{{end}}">
<input type="text" name="q"{{if .Query}} value="{{.Query}}"{{end}} placeholder="Search Posts" maxlength="255"{{if not .Query}} required{{end}}>
<input type="submit" value="q" title="Search">
</form>
{{end}}
<div class="post-list-outline">
<div id="postsz">
<div class="tab-container">
<div class="tab2">
<a{{if not .PopularPosts}} class="selected"{{end}} href="/communities/{{.Community.ID}}">All Posts</a>
<a{{if .PopularPosts}} class="selected"{{end}} href="/communities/{{.Community.ID}}/hot">Popular Posts</a>
</div>
</div>
{{if (and (and .CurrentUser.Username (not .PopularPosts)) (le .Community.Permissions .CurrentUser.Level))}}
<form id="post-form" method="post" action="/communities/{{.Community.ID}}/posts"{{if not .Repost.ID}} class="folded"{{end}} data-post-subtype="default" name="test-post-default-form">
<input type="hidden" name="community" value="{{.Community.ID}}">
<input type="hidden" name="csrfmiddlewaretoken" value="{{.CurrentUser.CSRFToken}}">
{{if .Repost.ID}}<input type="hidden" name="repost" value="{{.Repost.ID}}">
<p class="repost-notice">You are reposting <a class="topic-title" href="/posts/{{.Repost.ID}}" target="_blank">{{.Repost.Nickname}}'s post ({{.Repost.Text}})</a> along with this submission.</p>{{end}}
<div class="feeling-selector js-feeling-selector"><label class="symbol feeling-button feeling-button-normal checked"><input type="radio" name="feeling_id" value="0" checked><span class="symbol-label">normal</span></label><label class="symbol feeling-button feeling-button-happy"><input type="radio" name="feeling_id" value="1"><span class="symbol-label">happy</span></label><label class="symbol feeling-button feeling-button-like"><input type="radio" name="feeling_id" value="2"><span class="symbol-label">like</span></label><label class="symbol feeling-button feeling-button-surprised"><input type="radio" name="feeling_id" value="3"><span class="symbol-label">surprised</span></label><label class="symbol feeling-button feeling-button-frustrated"><input type="radio" name="feeling_id" value="4"><span class="symbol-label">frustrated</span></label><label class="symbol feeling-button feeling-button-puzzled"><input type="radio" name="feeling_id" value="5"><span class="symbol-label">puzzled</span></label></div>
<div class="textarea-with-menu active-text">
<menu class="textarea-menu">
<li><label class="textarea-menu-text"><input type="radio" name="post_type" value="0"></label></li>
<li><label class="textarea-menu-memo"><input type="radio" name="post_type" value="1"></label></li>
<li><label class="textarea-menu-poll"><input type="radio" name="post_type" value="2"></label></li>
<span class="character-count">2000</span>
</menu>
<div class="textarea-container">
<textarea name="body" class="textarea-text textarea" maxlength="2000" placeholder="Share your thoughts in a post to {{.Community.Title}}" data-open-folded-form data-required></textarea>
</div>
<div class="textarea-memo none">
<div id="memo-drawboard-page" class="none">
<div class="window-body">
<div class="memo-buttons">
<button type="button" class="artwork-clear"></button>
<button type="button" class="artwork-undo"></button>
<button type="button" class="artwork-pencil small selected"></button>
<button type="button" class="artwork-eraser small"></button>
<button type="button" class="artwork-fill"></button>
<input type="text" class="artwork-color">
<button type="button" class="artwork-zoom"></button>
</div>
<div class="memo-canvas">
<canvas id="artwork-canvas" zoom="2"></canvas>
<canvas id="artwork-canvas-undo"></canvas>
<canvas id="artwork-canvas-redo"></canvas>
<input type="hidden" name="painting">
</div>
<div class="form-buttons">
<input class="olv-modal-close-button black-button memo-finish-btn" type="button" value="Save">
<button type="button" class="artwork-lock none"></button>
</div>
</div>
</div>
</div>
<div class="textarea-poll none">
<button type="button" class="delete none" option="option-a"></button><input type="text" class="url-form option" name="option-a" placeholder="Option A" maxlength="64" data-required>
<button type="button" class="delete none" option="option-b"></button><input type="text" class="url-form option" name="option-b" placeholder="Option B" maxlength="64" data-required>
<button type="button" class="add-option symbol">Add Option</button>
</div>
</div>
{{if not (eq .MaxUploadSize "0")}}
<label class="file-button-container">
<span class="input-label">Attachment
<span>
Images, audio and videos are allowed.
{{if .MaxUploadSize}}Maximum upload size: {{.MaxUploadSize}}{{end}}
</span>
</span>
<span class="button file-upload-button">Upload</span>
<input accept="image/*, audio/*, video/*" type="file" class="file-button none">
<input type="hidden" name="image">
<input type="hidden" name="attachment_type">
<div class="screenshot-container still-image preview-container" style="display: none;">
<img class="preview-image none">
<video class="preview-video none" controls></video>
<audio class="preview-audio none" controls></audio>
</div>
<script src="/assets/js/upload.js"></script>
</label>
{{else}}
<input type="hidden" name="image">
{{end}}
<div class="post-form-footer-options">
<div class="post-form-footer-option-inner post-form-spoiler js-post-form-spoiler test-post-form-spoiler">
<label class="spoiler-button symbol"><input type="checkbox" id="is_spoiler" name="is_spoiler" value="1">Spoilers</label>
</div>
</div>
<div class="post-form-privacy">
<p>Who should be able to see this post?</p>
<select class="post-form-privacy-select" name="privacy">
<option value="0"{{if eq .CurrentUser.DefaultPrivacy 0}} selected{{end}}>Everyone</option>
<option value="1"{{if eq .CurrentUser.DefaultPrivacy 1}} selected{{end}}>Friends, Following and Followers</option>
<option value="2"{{if eq .CurrentUser.DefaultPrivacy 2}} selected{{end}}>Friends and Following</option>
<option value="3"{{if eq .CurrentUser.DefaultPrivacy 3}} selected{{end}}>Friends and Followers</option>
<option value="4"{{if eq .CurrentUser.DefaultPrivacy 4}} selected{{end}}>Friends Only</option>
<option value="5"{{if eq .CurrentUser.DefaultPrivacy 5}} selected{{end}}>Followers and Following</option>
<option value="6"{{if eq .CurrentUser.DefaultPrivacy 6}} selected{{end}}>Followers Only</option>
<option value="7"{{if eq .CurrentUser.DefaultPrivacy 7}} selected{{end}}>Following Only</option>
<option value="8"{{if eq .CurrentUser.DefaultPrivacy 8}} selected{{end}}>Admins Only</option>
<option value="9"{{if eq .CurrentUser.DefaultPrivacy 9}} selected{{end}}>Only Me</option>
</select>
</div>
<div class="form-buttons">
<input type="submit" class="black-button post-button disabled" value="Send" data-community-id="{{.Community.ID}}" data-post-content-type="text" data-post-with-screenshot="nodata" disabled>
</div>
</form>
{{end}}
<div class="body-content" id="community-post-list">
{{if .PopularPosts}}
<div class="pager-button">
{{if .PrevDate}}
<a class="button back-button symbol" href="/communities/{{.Community.ID}}/hot?date={{.PrevDate}}">
<span class="symbol-label"></span>
</a>
{{end}}
<a class="button selected" href="/communities/{{.Community.ID}}/hot">{{.CurrentDate}}</a>
<a class="button next-button symbol" href="/communities/{{.Community.ID}}/hot?date={{.NextDate}}">
<span class="symbol-label"></span>
</a>
</div>
{{end}}
{{end}}
<div class="list post-list js-post-list" data-next-page-url="{{if .Posts}}?{{if .PopularPosts}}date={{.CurrentDate}}{{end}}{{if .Query}}&q={{.Query}}{{end}}&offset={{.Offset}}&offset_time={{.OffsetTime}}{{end}}">
{{if .Posts}}
{{$user_id := .CurrentUser.ID}}
{{range $post := .Posts}}
{{template "render_post.html" $post}}
{{end}}
<div class="post-list-loading" style="padding: 20px">
<a class="black-button trigger" href="{{if .Posts}}?{{if .PopularPosts}}date={{.CurrentDate}}{{end}}{{if .Query}}&q={{.Query}}{{end}}&offset={{.Offset}}&offset_time={{.OffsetTime}}{{end}}">Load More Posts</a>
</div>
{{else}}
{{if .AutoPagerize}}
<div class="no-content no-post-content">
<p>This community doesn't have any posts yet.</p>
</div>
{{end}}
{{end}}
</div>
{{if .AutoPagerize}}
</div>
</div>
</div>
</div>
</div>
{{if .Pjax}}
{{template "footer.html"}}
{{end}}
{{end}}

96
views/conversation.html Normal file
View File

@ -0,0 +1,96 @@
{{if .Pjax}}
{{template "header.html" .}}
{{else}}
<title>{{.Title}}</title>
{{end}}
<div id="main-body">
{{template "general_sidebar.html" .}}
<div class="main-column{{if .IsGroupChat}} conversation{{end}}">
{{if .IsGroupChat}}<a href="/conversations/{{.ConversationID}}/edit"><button class="button msg-update">Edit Group</button></a>{{end}}
<form class="search{{if not .Query}} folded{{end}}">
<input type="text" name="q"{{if .Query}} value="{{.Query}}"{{end}} placeholder="Search Messages" maxlength="255"{{if not .Query}} required{{end}}>
<input type="submit" value="q" title="Search">
</form>
<div class="post-list-outline">
<h2 class="label">{{.Title}}</h2>
<form id="post-form" method="post" action="/messages" class="folded" data-post-subtype="default">
<input type="hidden" name="csrfmiddlewaretoken" value="{{.CurrentUser.CSRFToken}}">
<input type="hidden" name="conversation" value="{{.ConversationID}}">
<div class="feeling-selector js-feeling-selector"><label class="symbol feeling-button feeling-button-normal checked"><input type="radio" name="feeling_id" value="0" checked><span class="symbol-label">normal</span></label><label class="symbol feeling-button feeling-button-happy"><input type="radio" name="feeling_id" value="1"><span class="symbol-label">happy</span></label><label class="symbol feeling-button feeling-button-like"><input type="radio" name="feeling_id" value="2"><span class="symbol-label">like</span></label><label class="symbol feeling-button feeling-button-surprised"><input type="radio" name="feeling_id" value="3"><span class="symbol-label">surprised</span></label><label class="symbol feeling-button feeling-button-frustrated"><input type="radio" name="feeling_id" value="4"><span class="symbol-label">frustrated</span></label><label class="symbol feeling-button feeling-button-puzzled"><input type="radio" name="feeling_id" value="5"><span class="symbol-label">puzzled</span></label></div>
<div class="textarea-with-menu active-text">
<menu class="textarea-menu">
<li><label class="textarea-menu-text"><input type="radio" name="post_type" value="0"></label></li>
<li><label class="textarea-menu-memo"><input type="radio" name="post_type" value="1"></label></li>
<span class="character-count">2000</span>
</menu>
<div class="textarea-container">
<textarea name="body" class="textarea-text textarea" maxlength="2000" placeholder="Write a message here." data-open-folded-form data-required></textarea>
</div>
<div class="textarea-memo none">
<div id="memo-drawboard-page" class="none">
<div class="window-body">
<div class="memo-buttons">
<button type="button" class="artwork-clear"></button>
<button type="button" class="artwork-undo"></button>
<button type="button" class="artwork-pencil small selected"></button>
<button type="button" class="artwork-eraser small"></button>
<button type="button" class="artwork-fill"></button>
<input type="text" class="artwork-color">
<button type="button" class="artwork-zoom"></button>
</div>
<div class="memo-canvas">
<canvas id="artwork-canvas" zoom="2"></canvas>
<canvas id="artwork-canvas-undo"></canvas>
<canvas id="artwork-canvas-redo"></canvas>
<input type="hidden" name="painting">
</div>
<div class="form-buttons">
<input class="olv-modal-close-button black-button memo-finish-btn" type="button" value="Save">
<button type="button" class="artwork-lock none"></button>
</div>
</div>
</div>
</div>
</div>
<label class="file-button-container">
<span class="input-label">Attachment
<span>
Images, audio and videos are allowed.
{{if .MaxUploadSize}}Maximum upload size: {{.MaxUploadSize}}{{end}}
</span>
</span>
<span class="button file-upload-button">Upload</span>
<input accept="image/*, audio/*, video/*" type="file" class="file-button none">
<input type="hidden" name="image">
<input type="hidden" name="attachment_type">
<div class="screenshot-container still-image preview-container" style="display:none">
<img class="preview-image none">
<video class="preview-video none" controls></video>
<audio class="preview-audio none" controls></audio>
</div>
<script src="/assets/js/upload.js"></script>
</label>
<div class="form-buttons">
<input type="submit" class="black-button post-button disabled" value="Send" data-post-content-type="text" data-post-with-screenshot="nodata" disabled>
</div>
</form>
<div class="list messages"{{if .Messages}} data-next-page-url="/{{if .IsGroupChat}}conversations/{{.ConversationID}}{{else}}messages/{{.User.Username}}{{end}}?{{if .Query}}q={{.Query}}{{end}}&offset={{.Offset}}&offset_time={{.OffsetTime}}"{{end}}>
{{if .Messages}}
{{$username := .CurrentUser.Username}}
{{range $message := .Messages}}
{{template "render_message.html" $message}}
{{end}}
{{else}}
{{if eq .Offset 20}}
<div class="no-content">
<p>No messages.</p>
</div>
{{end}}
{{end}}
</div>
</div>
</div>
</div>
{{if .Pjax}}
{{template "footer.html"}}
{{end}}

113
views/create_group.html Normal file
View File

@ -0,0 +1,113 @@
{{if eq .Offset 20}}
{{if .Pjax}}
{{template "header.html" .}}
<meta property="og:description" content="{{.Sidebar.Profile.Comment}}">
{{else}}
<title>{{.Title}} - Indigo</title>
{{end}}
<div id="main-body" class="messages">
{{template "general_sidebar.html" .}}
<div class="main-column">
<div class="post-list-outline">
<h2 class="label">
{{if .Editing}}<form action="/conversations/{{.ConversationID}}/{{if .ByMe}}delete{{else}}leave{{end}}" method="post"><input type="hidden" name="csrfmiddlewaretoken" value="{{.CurrentUser.CSRFToken}}"><button type="submit" class="button msg-update">{{if .ByMe}}Delete{{else}}Leave{{end}} Group</button></form>{{end}}
Users Added
</h2>
<form method="post">
<input type="hidden" name="csrfmiddlewaretoken" value="{{.CurrentUser.CSRFToken}}">
<div class="list follow-list">
<ul class="list-content-with-icon-and-text arrow-list" id="members">
{{if .Members}}
{{range $user := .Members}}
<li class="trigger" data-href="/users/{{$user.Username}}">
<a href="/users/{{$user.Username}}" username="{{$user.Username}}" class="icon-container
{{if not $user.HideOnline}}
{{if $user.Online}}
online
{{else}}
offline
{{end}}
{{end}}
{{if $user.Role.Image}} official-user"><img src="{{$user.Role.Image}}" class="official-tag">{{else}}">{{end}}
<img src="{{$user.Avatar}}" class="icon"></a>
<div class="toggle-button">
<button type="button" class="follow-button button symbol relationship-button none">Add</button>
<button type="button" class="button follow-done-button relationship-button symbol">Remove</button>
<input class="input" type="hidden" name="user{{$user.Level}}" value="{{$user.Username}}">
</div>
<div class="body">
<p class="title">
<span class="nick-name"><a href="/users/{{$user.Username}}"{{if $user.Color}} style="color:{{$user.Color}}"{{end}}>{{$user.Nickname}}</a></span>
<span class="id-name">{{$user.Username}}</span>
</p>
<p class="text">{{$user.Comment}}</p>
</div>
</li>
{{end}}
<div class="no-content none">
<p>You haven't added any users to your group yet.</p>
</div>
{{else}}
{{if eq .Offset 20}}
<div class="no-content">
<p>You haven't added any users to your group yet.</p>
</div>
{{end}}
{{end}}
</ul>
</div>
<div class="form-buttons">
<input type="submit" class="black-button post-button disabled" value="{{if .Editing}}Edit{{else}}Create{{end}} Group" disabled>
</div>
</form>
<h2 class="label">Add Friends</h2>
<div class="list follow-list">
{{end}}
<ul class="list-content-with-icon-and-text arrow-list" id="friends" data-next-page-url="{{if .Users}}?&offset={{.Offset}}{{end}}">
{{if .Friends}}
{{range $user := .Friends}}
<li class="trigger" data-href="/users/{{$user.Username}}">
<a href="/users/{{$user.Username}}" class="icon-container
{{if not $user.HideOnline}}
{{if $user.Online}}
online
{{else}}
offline
{{end}}
{{end}}
{{if $user.Role.Image}} official-user"><img src="{{$user.Role.Image}}" class="official-tag">{{else}}">{{end}}
<img src="{{$user.Avatar}}" class="icon"></a>
<div class="toggle-button">
<button type="button" class="follow-button button symbol relationship-button">Add</button>
<button type="button" class="button follow-done-button relationship-button symbol none">Remove</button>
<input class="input" type="hidden" value="{{$user.Username}}">
</div>
<div class="body">
<p class="title">
<span class="nick-name"><a href="/users/{{$user.Username}}"{{if $user.Color}} style="color:{{$user.Color}}"{{end}}>{{$user.Nickname}}</a></span>
<span class="id-name">{{$user.Username}}</span>
</p>
<p class="text">{{$user.Comment}}</p>
</div>
</li>
{{end}}
<div class="no-content none">
<p>You've added every friend you can to the group.</p>
</div>
{{else}}
{{if eq .Offset 20}}
<div class="no-content">
<p>{{if .FriendCount}}You've added every friend you can to the group.{{else}}You don't have any friends. Why don't you make some?{{end}}</p>
</div>
{{end}}
{{end}}
</ul>
{{if eq .Offset 20}}
</div>
</div>
</div>
</div>
{{if .Pjax}}
{{template "footer.html"}}
{{end}}
{{end}}

View File

@ -0,0 +1,102 @@
<li id="{{.Comment.ID}}" data-href{{if and .IsSpoiler (not .ByMii)}}-hidden{{end}}="/comments/{{.Comment.ID}}" class="post {{if .ByMe}}my{{else}}other{{end}} trigger{{if and .IsSpoiler (not .ByMii)}} hidden{{end}}">
<a href="/users/{{.Comment.CommenterUsername}}" username="{{.Comment.CommenterUsername}}" class="icon-container{{if not .Comment.CommenterHideOnline}} online{{end}}{{if .Comment.CommenterRoleImage}} official-user"><img src="{{.Comment.CommenterRoleImage}}" class="official-tag">{{else}}">{{end}}<img src="{{.Comment.CommenterIcon}}" class="icon"></a>
<div class="body">
<div class="header">
<p class="user-name"><a href="/users/{{.Comment.CommenterUsername}}"{{if .Comment.CommenterColor}} style="color:{{.Comment.CommenterColor}}"{{end}}>{{.Comment.CommenterNickname}}</a></p>
<p class="timestamp-container">
<a class="timestamp" href="/comments/{{.Comment.ID}}">
<span class="update" time="{{.Comment.CreatedAtUnix}}000">{{.Comment.CreatedAt}}</span>
{{if .Comment.EditedAt}}
(Edited <span class="update" time="{{.Comment.EditedAtUnix}}000">{{.Comment.EditedAt}}</span>)
{{end}}
</a>
<span class="spoiler-status{{if .Comment.IsSpoiler}} spoiler{{end}}"> · Spoilers</span>
</p>
</div>
{{if eq .Comment.PostType 1}}
<div class="reply-content-memo">
<img class="reply-memo" src="{{.Comment.BodyText}}">
</div>
{{else}}
<div class="reply-content-text">{{.Comment.Body}}</div>
{{end}}
{{if .Comment.Image}}
{{if eq .Comment.AttachmentType 1}}
<div class="screenshot-container">
<audio controls preload="none" src="{{.Comment.Image}}"></audio>
</div>
{{else if eq .Comment.AttachmentType 2}}
<div class="screenshot-container video">
<video controls preload="none" src="{{.Comment.Image}}"></video>
</div>
{{else}}
<div class="screenshot-container still-image">
<img src="{{.Comment.Image}}">
</div>
{{end}}
{{end}}
{{if .Comment.URL}}
{{if eq .Comment.URLType 1}}
<div class="screenshot-container video">
<iframe src="https://www.youtube.com/embed/{{.Comment.URL}}" width="490" height="276" frameborder="0" allowfullscreen="true"></iframe>
</div>
{{else if eq .Comment.URLType 2}}
<div class="screenshot-container video">
<iframe src="https://open.spotify.com/embed/track/{{.Comment.URL}}" width="490" height="276" frameborder="0" allow="encrypted-media"></iframe>
</div>
{{else if eq .Comment.URLType 3}}
<div class="screenshot-container video audio">
<iframe class="audio" src="https://w.soundcloud.com/player/?url={{.Comment.URL}}&auto_play=false&show_artwork=true&color=8000ff" frameborder="0"></iframe>
</div>
{{else}}
<p class="url-link">
<a class="link-confirm" href="{{.Comment.URL}}" target="_blank">{{.Comment.URL}}</a>
</p>
{{end}}
{{end}}
{{if and .Comment.IsSpoiler (not .Comment.ByMii)}}
<div class="hidden-content">
<p>This comment contains spoilers.</p>
<button type="button" class="hidden-content-button">View Post</button>
</div>
{{end}}
<div class="reply-meta">
<button type="button" {{if not .CanYeah}} disabled{{end}} class="symbol submit yeah-button
{{if not .CanYeah}} disabled{{end}}
" data-is-in-reply-list="1" data-feeling="{{.Comment.Feeling}}" data-action="/comments/{{.Comment.ID}}/yeah" data-url-id="{{.Comment.ID}}"><span class="yeah-button-text">
{{if .Comment.Yeahed}}
{{if eq .Comment.Feeling 6}}
Unepic
{{else if eq .Comment.Feeling 7}}
Unnyeah
{{else if eq .Comment.Feeling 8}}
Unyes
{{else if eq .Comment.Feeling 9}}
olv.portal.miitoo.delete
{{else}}
Unyeah
{{end}}
{{else}}
{{if eq .Comment.Feeling 2}}
Yeah♥
{{else if eq .Comment.Feeling 3}}
Yeah!?
{{else if or (eq .Comment.Feeling 4) (eq .Comment.Feeling 5)}}
Yeah...
{{else if eq .Comment.Feeling 6}}
Epic!
{{else if eq .Comment.Feeling 7}}
Nyeah~♥
{{else if eq .Comment.Feeling 8}}
Yes!
{{else if eq .Comment.Feeling 9}}
olv.portal.miitoo.
{{else}}
Yeah!
{{end}}
{{end}}
</span></button>
<div class="yeah symbol"><span class="symbol-label">Yeahs</span><span class="yeah-count">0</span></div>
</div>
</div>
</li>

View File

@ -0,0 +1,13 @@
</div>
<div id="footer">
<div id="footer-inner">
<div class="link-container">
<p><a href="https://archive.org/details/doom1-sw1">play doom in your browser!</a></p>
<p><a href="https://www.dell.com/support/incidents-online/en-us/contactus/dynamic">Contact Us</a></p>
<p>"To protect the world from devastation!"</p>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,33 @@
<div id="sidebar" class="user-sidebar">
{{if .CurrentUser.Username}}
<div class="sidebar-container">
<div id="sidebar-profile-body">
<div username="{{.CurrentUser.Username}}" class="icon-container
{{if not .CurrentUser.HideOnline}}
{{if .CurrentUser.Online}}
online
{{else}}
offline
{{end}}
{{end}}
{{if .CurrentUser.Role.Image}} official-user"><img src="{{.CurrentUser.Role.Image}}" class="official-tag">{{else}}">{{end}}
<a href="/users/{{.CurrentUser.Username}}"><img src="{{.CurrentUser.Avatar}}" alt="{{.CurrentUser.Username}}" class="icon"></a>
</div>
{{if .CurrentUser.Role.Organization}}<p class="user-organization">{{.CurrentUser.Role.Organization}}</p>{{end}}
<a href="/users/{{.CurrentUser.Username}}" class="nick-name"{{if .CurrentUser.Color}} style="color:{{.CurrentUser.Color}}"{{end}}>{{.CurrentUser.Nickname}}</a>
<p class="id-name">{{.CurrentUser.Username}}</p>
</div>
<button class="button" onclick="Olv.Closed.lights()">Toggle Dark Mode</button>
<ul id="sidebar-profile-status">
<li><a href="/users/{{.CurrentUser.Username}}/friends"><span><span class="number test-friend-count">{{.FriendCount}}</span>Friends</span></a></li>
<li><a href="/users/{{.CurrentUser.Username}}/following"><span><span class="number test-following-count">{{.FollowingCount}}</span>Following</span></a></li>
<li><a href="/users/{{.CurrentUser.Username}}/followers"><span><span class="number test-follower-count">{{.FollowerCount}}</span>Followers</span></a></li>
</ul>
</div>
{{end}}
<div class="sidebar-container sidebar-setting">
<ul>
<li><a href="/help/rules" class="sidebar-menu-info symbol"><span>rules</span></a></li>
</ul>
</div>
</div>

View File

@ -0,0 +1,88 @@
<!DOCTYPE html>
<html lang="en" class="os-mac">
<head>
<meta charset="utf-8">
<title>sop.epic</title>
<meta http-equiv="content-style-type" content="text/css">
<meta http-equiv="content-script-type" content="text/javascript">
<meta http-equiv="content-security-policy" content="block-all-mixed-content">
<meta name="format-detection" content="telephone=no">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="apple-mobile-web-app-title" content="sop.epic">
<meta name="description" content="SOP.epic is a small scale feature pakced Twitter alternative with no paywalls">
<meta property="og:locale" content="en_US">
<meta property="og:title" content="{{.Title}}">
<meta property="og:type" content="article">
<meta property="og:image" content="/assets/img/favicon.png">
<meta property="og:site_name" content="sop.epic">
<meta property="article:published_time" content="None">
<link rel="shortcut icon" type="image/png" href="/assets/img/favicon.png">
<link rel="stylesheet" type="text/css" href="/assets/css/offdevice.css">
{{if .CurrentUser.Theme}}
<style id="theme">
:root {
--theme: {{index .CurrentUser.ThemeColors 0}};
--theme-light: {{index .CurrentUser.ThemeColors 1}};
--theme-dark: {{index .CurrentUser.ThemeColors 2}};
--theme-darker: {{index .CurrentUser.ThemeColors 3}};
}
</style>
<link rel="stylesheet" type="text/css" href="/assets/css/color.css">
{{end}}
<link id="darkness" {{if .CurrentUser.LightMode}}disabled{{end}} rel="stylesheet" type="text/css" href="/assets/css/dark.css">
<script src="/assets/js/jslibs.js"></script>
<script src="/assets/js/indigo.js"></script>
{{if .CurrentUser.WebsocketsEnabled}}
<script id="websockets" src="/assets/js/websocket.js"></script>
{{else}}
<script>var websocketsEnabled = false;</script>
{{end}}
</head>
<body class="{{if not .CurrentUser.Username}}guest"{{else}}" sess-usern="{{.CurrentUser.Username}}"{{end}} csrf-token="{{.CurrentUser.CSRFToken}}">
<div id="wrapper" class="{{if not .CurrentUser.Username}}guest{{end}}">
<div id="sub-body">
<menu id="global-menu">
<li id="global-menu-logo"><h1><a href="/"><img src="/assets/img/menu-logo.png" alt="Indigo"></a></h1></li>
{{if not .CurrentUser.Username}}
<li id="global-menu-login">
<a href="/login" class="login">
<input type="image" alt="Sign in" src="/assets/img/sign-in.png">
</a>
</li>
{{else}}
<li id="global-menu-list">
<ul>
<li id="global-menu-mymenu">
<a href="/users/{{.CurrentUser.Username}}">
<span class="icon-container">
<img src="{{.CurrentUser.Avatar}}" alt="User Page"{{if .CurrentUser.Role.Image}} class="official-user"><img src="{{.CurrentUser.Role.Image}}" class="official-tag">{{else}}>{{end}}
</span>
<span>my profile</span>
</a>
</li>
<li id="global-menu-feed"><a href="/activity"><span>timeline!</span></a></li>
<li id="global-menu-community"><a href="/communities/1"><span>plaza!</span></a></li>
<li id="global-menu-message"><a href="/messages"><span>messages!</span><span class="badge"{{if not .CurrentUser.Notifications.Messages}} style="display: none;"{{end}}>{{.CurrentUser.Notifications.Messages}}</span></a></li>
<li id="global-menu-news"><a href="/notifications"><span class="badge"{{if not .CurrentUser.Notifications.Notifications}} style="display: none;"{{end}}>{{.CurrentUser.Notifications.Notifications}}</span><span>notifications!</span></a></li>
<li id="global-menu-my-menu"><button class="symbol js-open-global-my-menu open-global-my-menu"></button>
<menu id="global-my-menu" class="invisible none">
<li><a href="/settings/profile" class="symbol my-menu-profile-setting"><span>profile settings</span></a></li>
<li><a href="#" class="symbol my-menu-account-setting"><span>account settings</span></a></li>
<li><a href="/help/rules" class="symbol my-menu-guide"><span>SOP rules</span></a></li>
<li><a href="/communities/2" class="symbol my-menu-guide"><span>piracy cabal network</span></a></li>
<li><a href="/blocked" class="symbol my-menu-block"><span>blocklist</span></a></li>
{{if gt .CurrentUser.Level 0}}<li><a href="/admin" class="symbol my-menu-info"><span>$10 mode</span></a></li>{{end}}
<li>
<form action="/logout" method="post" id="my-menu-logout" class="symbol">
<input type="hidden" name="csrfmiddlewaretoken" value="{{.CurrentUser.CSRFToken}}">
<input type="submit" value="Log out">
</form>
</li>
</menu>
</li>
</ul>
</li>
{{end}}
</menu>
</div>
<div id="container">

8
views/elements/poll.html Normal file
View File

@ -0,0 +1,8 @@
<div class="post-poll{{if .Votes}} has-votes{{end}}" data-action="/posts/{{.ID}}/vote">
<div class="poll-options">
<a class="poll-votes">{{.Votes}} vote{{if not (eq .Votes 1.0)}}s{{end}}</a>
{{range $option := .Options}}
<a class="poll-option{{if $option.Selected}} selected{{end}}" option-id="{{$option.ID}}" votes="{{$option.Votes}}"><div class="poll-background" style="width:{{$option.Percentage}}%"></div><span class="option-name" original="{{$option.Name}}">{{$option.Name}}</span><span class="percentage">{{$option.Percentage}}%<span class="vote-count"> ({{$option.Votes}} vote{{if not (eq .Votes 1.0)}}s{{end}})</span></span></a>
{{end}}
</div>
</div>

View File

@ -0,0 +1,335 @@
{{if and (.User.Theme) (not (eq .ProfileOnPage "settings"))}}
<style id="theme">
:root {
--theme: {{index .User.ThemeColors 0}};
--theme-light: {{index .User.ThemeColors 1}};
--theme-dark: {{index .User.ThemeColors 2}};
--theme-darker: {{index .User.ThemeColors 3}};
}
</style>
<link rel="stylesheet" type="text/css" href="/assets/css/color.css">
{{end}}
<div id="sidebar" class="user-sidebar{{if eq .ProfileOnPage "main"}} profile-top{{end}}">
<div class="sidebar-container">
{{if .Profile.FavoritePostID}}
<a href="/posts/{{.Profile.FavoritePostID}}" id="sidebar-cover" style="background-image:url({{.Profile.FavoritePostImage}})">
<img src="{{.Profile.FavoritePostImage}}" class="sidebar-cover-image">
</a>
{{end}}
<div id="sidebar-profile-body"{{if .Profile.FavoritePostID}} class="with-profile-post-image"{{end}}>
<div username="{{.User.Username}}" class="icon-container
{{if not .User.HideOnline}}
{{if .User.Online}}
online
{{else}}
offline
{{end}}
{{end}}
{{if .User.Role.Image}}official-user"><img src="{{.User.Role.Image}}" class="official-tag">{{else}}">{{end}}
<a href="/users/{{.User.Username}}"><img src="{{.User.Avatar}}" alt="{{.User.Username}}" class="icon"></a>
</div>
{{if .User.Role.Organization}}<p class="user-organization">{{.User.Role.Organization}}</p>{{end}}
<a href="/users/{{.User.Username}}" class="nick-name"{{if .User.Color}} style="color:{{.User.Color}}"{{end}}>{{.User.Nickname}}</a>
<p class="id-name">{{.User.Username}}</p>
</div>
{{if (and (not (eq .User.ID .CurrentUser.ID)) (.CurrentUser.Username))}}
<div class="user-action-content">
<div class="toggle-button">
<button type="button" data-action="/users/{{.User.Username}}/follow" class="follow-button button symbol{{if .IsFollowing}} none{{end}}">Follow</button>
<button type="button" data-action="/users/{{.User.Username}}/unfollow" class="unfollow-button button symbol{{if not .IsFollowing}} none{{end}}" data-screen-name="{{.User.Nickname}}">Follow</button>
{{if and (eq .FriendStatus 0) (or (eq .Profile.AllowFriend 0) (and (eq .Profile.AllowFriend 1) .IsFollowingMe))}}<button type="button" data-action="/users/{{.User.Username}}/friend_new" class="friend-button create button symbol">Send friend request</button>{{end}}
{{if eq .FriendStatus 1}}<button type="button" data-action="/users/{{.User.Username}}/friend_accept" data-screen-name="{{.User.Nickname}}" data-time="{{.RequestTime}}" data-msg="q" class="friend-button accept button symbol">Become friends</button>{{end}}
{{if eq .FriendStatus 2}}<button type="button" data-action="/users/{{.User.Username}}/friend_cancel" data-screen-name="{{.User.Nickname}}" class="friend-button unf cancel button symbol">Cancel friend request</button>{{end}}
{{if eq .FriendStatus 3}}<button type="button" data-action="/users/{{.User.Username}}/friend_delete" data-screen-name="{{.User.Nickname}}" class="friend-button unf delete button symbol">Friends</button>{{end}}
{{if le .User.Level 0}}
<div class="report-buttons-content">
<button type="button" class="report-button report-user" data-track-label="user" data-track-action="openReportModal" data-track-category="reportViolator" data-modal-open="#report-violator-page">Report Violation</button>
<button type="button" class="report-button block-button {{if .User.Blocked}}unblock">Unblock{{else}}block">Block{{end}}</button>
</div>
{{end}}
</div>
{{if eq .FriendStatus 0}}
<div class="dialog none" data-modal-types="post-friend-request">
<div class="dialog-inner">
<div class="window">
<h1 class="window-title">Send friend request to {{.User.Nickname}}</h1>
<div class="window-body">
<p class="description">
Friend Request: <img width="36px" height="36px" src="{{.User.Avatar}}">{{.User.Nickname}}
</p>
<form method="post">
<textarea name="body" class="textarea" maxlength="2000" data-placeholder="Write a friend request here." placeholder="Write a friend request here."></textarea>
<div class="form-buttons">
<input type="button" class="olv-modal-close-button gray-button" value="Cancel">
<input type="submit" value="Send" class="post-button black-button">
</div>
</form>
</div>
</div>
</div>
</div>
{{else if eq .FriendStatus 1}}
<div class="dialog none" data-modal-types="accept-friend-request" data-screen-name="{{.User.Nickname}}" data-reject-action="/users/{{.User.Username}}/friend_reject" data-action="/users/{{.User.Username}}/friend_accept" uuid="{{.Request.ID}}">
<div class="dialog-inner">
<div class="window">
<h1 class="window-title">Friend Request from {{.User.Nickname}} at {{.Request.CreatedAt}}</h1>
<div class="window-body">
<div id="sidebar-profile-body">
<div username="{{.User.Username}}" class="icon-container {{if not .User.HideOnline}}{{if .User.Online}}online{{else}}offline{{end}}{{end}}{{if .User.Role.Image}} official-user"><img src="{{.User.RoleImage}}" class="official-tag">{{else}}">{{end}}
<a href="/users/{{.User.Username}}"><img src="{{.User.Avatar}}" alt="{{.User.Nickname}}" class="icon"></a>
</div>
{{if .User.Role.Organization}}<p class="user-organization">{{.User.Role.Organization}}</p>{{end}}
<a href="/users/{{.User.Username}}" class="nick-name"{{if .User.Color}} style="color:{{.User.Color}}"{{end}}>{{.User.Nickname}}</a>
<p class="id-name">{{.User.Username}}</p>
</div>
{{if .Request.Message}}<pre>{{.Request.Message}}</pre>{{end}}
<p class="window-body-content">Accept {{.User.Nickname}}'s friend request?</p>
<div class="form-buttons three">
<button class="olv-modal-close-button gray-button" data-event-type="cancel" type="button">Cancel</button>
<button class="cancel-button gray-button" type="button">Reject</button>
<button class="ok-button post-button black-button" data-event-type="ok" type="button">Accept</button>
</div>
</div>
</div>
</div>
</div>
{{end}}
</div>
<div id="report-violator-page" class="dialog none" data-modal-types="report report-violator" data-is-template="1">
<div class="dialog-inner">
<div class="window">
<h1 class="window-title">Report this User to Indigo Administration</h1>
<div class="window-body">
<form method="post" action="/users/{{.User.Username}}/violators">
<input type="hidden" name="csrfmiddlewaretoken" value="{{.CurrentUser.CSRFToken}}">
<p class="description">You are about to report a user for violating the Indigo Rules. This report will be sent to the Indigo administration and not to the user you are reporting.</p>
<div class="select-content">
<span class="select-button-label">Violation Type: </span>
<div class="select-button test-report-type-button">
<select name="type">
<option selected value>Make a selection.</option>
{{range $index, $reason := .Reasons}}
{{if $reason.Enabled}}
<option value="{{$index}}"{{if $reason.BodyRequired}} data-body-required="1"{{end}}>{{$reason.Name}}</option>
{{end}}
{{end}}
</select>
</div>
</div>
<textarea name="body" class="textarea" maxlength="100" placeholder="Enter a reason for the report here."></textarea>
<p class="violator-id">Username: {{.User.Username}}</p>
<div class="form-buttons">
<input type="button" class="olv-modal-close-button gray-button" value="Cancel">
<input type="submit" class="post-button black-button test-report-submit" value="Submit Report" data-community-id data-url-id data-track-label="user" data-title-id data-track-action="openReportModal" data-track-category="reportViolator">
</div>
</form>
</div>
</div>
</div>
</div>
{{if not .User.Blocked}}
<div class="dialog none" data-modal-types="post-block">
<div class="dialog-inner">
<div class="window">
<h1 class="window-title">Block {{.User.Nickname}}</h1>
<div class="window-body">
<div id="sidebar-profile-body">
<div username="{{.User.Username}}" class="icon-container{{if not .User.HideOnline}}{{if .User.Online}} online{{else}} offline{{end}}{{end}}
{{if .User.Role.Image}} official-user"><img src="{{.User.Role.Image}}" class="official-tag">{{else}}">{{end}}
<a href="/users/{{.User.Username}}">
<img src="{{.User.Avatar}}" alt="{{.User.Nickname}}" class="icon">
</a>
</div>
<a href="/users/{{.User.Username}}" class="nick-name">{{.User.Nickname}}</a>
<p class="id-name">{{.User.Username}}</p>
</div>
<form method="post" data-action="/users/{{.User.Username}}/block">
<p class="window-body-content">Really block this user? You won't be able to see each other's posts or profile.</p>
<div class="form-buttons">
<input type="button" class="olv-modal-close-button gray-button" value="No">
<input type="submit" value="Yes" class="post-button black-button">
</div>
</form>
</div>
</div>
</div>
</div>
{{else}}
<div class="dialog none" data-modal-types="post-unblock">
<div class="dialog-inner">
<div class="window">
<h1 class="window-title">Unblock {{.User.Nickname}}</h1>
<div class="window-body">
<div id="sidebar-profile-body">
<div username="{{.User.Username}}" class="icon-container{{if .User.Online}} online{{else}} offline{{end}}
{{if .User.Role.Image}} official-user"><img src="{{.User.Role.Image}}" class="official-tag">{{else}}">{{end}}
<a href="/users/{{.User.Username}}">
<img src="{{.User.Avatar}}" alt="{{.User.Nickname}}" class="icon">
</a>
</div>
<a href="/users/{{.User.Username}}" class="nick-name"{{if .User.Color}} style="color:{{.User.Color}}"{{end}}>{{.User.Nickname}}</a>
<p class="id-name">{{.User.Username}}</p>
</div>
<form method="post" data-action="/users/{{.User.Username}}/unblock">
<p class="window-body-content">Unblock this user?</p>
<div class="form-buttons">
<input type="button" class="olv-modal-close-button gray-button" value="No">
<input type="submit" value="Yes" class="post-button black-button">
</div>
</form>
</div>
</div>
</div>
</div>
{{end}}
{{else if .CurrentUser.Username}}
<div id="edit-profile-settings"><a class="button symbol" href="/settings/profile">Profile Settings</a></div>
<button class="button" onclick="Olv.Closed.lights()">Toggle dark mode</button>
{{end}}
<ul id="sidebar-profile-status">
<li><a href="/users/{{.User.Username}}/friends" class="
{{if .ProfileOnPage}}
{{if eq .ProfileOnPage "friends"}}selected{{end}}
{{end}}
"><span><span class="number test-following-count">{{$.Profile.FriendCount}}</span>Friends</span></a></li>
<li><a href="/users/{{.User.Username}}/following" class="
{{if .ProfileOnPage}}
{{if eq .ProfileOnPage "following"}}selected{{end}}
{{end}}
"><span><span class="number test-following-count">{{$.Profile.FollowingCount}}</span>Following</span></a></li>
<li><a href="/users/{{.User.Username}}/followers" class="
{{if .ProfileOnPage}}
{{if eq .ProfileOnPage "followers"}}selected{{end}}
{{end}}
"><span><span class="number test-follower-count">{{$.Profile.FollowerCount}}</span>Followers</span></a></li>
</ul>
</div>
<div class="sidebar-setting sidebar-container">
<div class="sidebar-post-menu">
<a href="/users/{{.User.Username}}/posts" class="sidebar-menu-post with-count symbol{{if .ProfileOnPage}}{{if eq .ProfileOnPage "posts"}} selected{{end}}{{end}}"><span>All posts</span><span class="post-count"><span class="test-post-count">{{$.Profile.PostCount}}</span></a>
<a href="/users/{{.User.Username}}/comments" class="sidebar-menu-replies with-count symbol{{if .ProfileOnPage}}{{if eq .ProfileOnPage "comments"}} selected{{end}}{{end}}"><span>All comments</span><span class="post-count"><span class="test-reply-count">{{$.Profile.CommentCount}}</span></a>
<a href="/users/{{.User.Username}}/yeahs" class="sidebar-menu-empathies with-count symbol{{if .ProfileOnPage}}{{if eq .ProfileOnPage "yeahs"}} selected{{end}}{{end}}"><span>Yeahs given</span><span class="post-count"><span class="test-empathy-count">{{$.Profile.YeahCount}}</span></a>
</div>
</div>
<div class="sidebar-container sidebar-profile">
{{if .Profile.Comment}}
<div class="profile-comment">
<div class="js-truncated-text">
{{.Profile.Comment}}
</div>
</div>
{{end}}
<div class="user-data">
{{if .Profile.Region}}
<div class="data-content">
<h4><span>Region</span></h4>
<div class="note">
<span>{{.Profile.Region}}</span>
</div>
</div>
{{end}}
{{if .Profile.NNID}}
{{if (or (eq .Profile.NNIDVisibility 0) (or (or (and (eq .Profile.NNIDVisibility 1) (eq .FriendStatus 3)) (and (eq .Profile.NNIDVisibility 1) (eq .Profile.User .CurrentUser.ID))) (and (eq .Profile.NNIDVisibility 2) (eq .Profile.User .CurrentUser.ID))))}}
<div class="data-content">
<h4><span>NNID</span></h4>
<div class="note">
<span>{{.Profile.NNID}}</span>
</div>
</div>
{{end}}
{{end}}
{{if .Profile.Twitter}}
<div class="data-content">
<h4><span>Twitter</span></h4>
<div class="note">
<span><a href="https://twitter.com/{{.Profile.Twitter}}" target="_blank">@{{.Profile.Twitter}}</a></span>
</div>
</div>
{{end}}
{{if .Profile.Discord}}
<div class="data-content">
<h4><span>DiscordTag</span></h4>
<div class="note">
<span>{{.Profile.Discord}}</span>
</div>
</div>
{{end}}
{{if .Profile.SwitchCode}}
<div class="data-content">
<h4><span>Switch FC</span></h4>
<div class="note">
<span>{{.Profile.SwitchCode}}</span>
</div>
</div>
{{end}}
{{if .Profile.PSN}}
<div class="data-content">
<h4><span>PlayStation Network</span></h4>
<div class="note">
<span>{{.Profile.PSN}}</span>
</div>
</div>
{{end}}
{{if .Profile.YouTube}}
<div class="data-content">
<h4><span>URL</span></h4>
<div class="note">
<span><a href="{{.Profile.YouTube}}" target="_blank">{{.Profile.YouTube}}</a></span>
</div>
</div>
{{end}}
{{if .Profile.Steam}}
<div class="data-content">
<h4><span>Steam</span></h4>
<div class="note">
<span>{{.Profile.Steam}}</span>
</div>
</div>
{{end}}
<div class="data-content">
<h4><span>User ID</span></h4>
<div class="note">
<span>#{{.User.ID}}</span>
</div>
</div>
<div class="data-content">
<h4><span>Joined At</span></h4>
<div class="note">
<span class="update" time="{{.Profile.CreatedAtUnix}}000">{{.Profile.CreatedAt}}</span>
</div>
</div>
{{if not .User.HideLastSeen}}
<div class="data-content">
<h4><span>Last seen</span></h4>
<div class="note">
<span class="update" time="{{.User.LastSeenUnix}}000">{{.User.LastSeen}}</span>
</div>
</div>
{{end}}
{{if .Profile.Gender}}
<div class="data-content">
<h4><span>Gender</span></h4>
<div class="note">
<span>{{.Profile.Gender}}</span>
</div>
</div>
{{end}}
</div>
</div>
{{if .FavoriteCommunities}}
<div class="sidebar-container sidebar-favorite-community">
<h4><a href="/users/{{.User.Username}}/favorites" class="favorite-community-button symbol"><span>Favorite Communities</span></a></h4>
<ul>
{{range $community := .FavoriteCommunities}}
<li class="favorite-community">
<a href="/communities/{{$community.ID}}">
<span class="icon-container">
<img class="icon" src="{{$community.Icon}}">
</span>
</a>
</li>
{{end}}
</ul>
</div>
{{end}}
</div>

View File

@ -0,0 +1,126 @@
<li id="{{.ID}}"{{if not .IsRMByAdmin}} data-href{{if and .IsSpoiler (not .ByMii)}}-hidden{{end}}="/comments/{{.ID}}"{{end}} class="post {{if .ByMe}}my{{else}}other{{end}}{{if not .IsRMByAdmin}} trigger{{end}}{{if and (or .IsSpoiler .IsRMByAdmin) (not .ByMii)}} hidden{{end}}">
<a href="/users/{{.CommenterUsername}}" username="{{.CommenterUsername}}" class="icon-container
{{if not .CommenterHideOnline}}
{{if .CommenterOnline}}
online
{{else}}
offline
{{end}}
{{end}}
{{if .CommenterRoleImage}} official-user"><img src="{{.CommenterRoleImage}}" class="official-tag">{{else}}">{{end}}
<img src="{{.CommenterIcon}}" class="icon">
</a>
<div class="body">
<div class="header">
<p class="user-name"><a href="/users/{{.CommenterUsername}}"{{if .CommenterColor}} style="color:{{.CommenterColor}}"{{end}}>{{.CommenterNickname}}</a></p>
<p class="timestamp-container">
<a class="timestamp"{{if not .IsRMByAdmin}} href="/comments/{{.ID}}"{{end}}>
<span class="update" time="{{.CreatedAtUnix}}000">{{.CreatedAt}}</span>
{{if .EditedAt}}
(Edited <span class="update" time="{{.EditedAtUnix}}000">{{.EditedAt}}</span>)
{{end}}
</a>
<span class="spoiler-status{{if .Pinned}} spoiler{{end}}"> · Pinned</span>
<span class="spoiler-status{{if .IsSpoiler}} spoiler{{end}}"> · Spoilers</span>
</p>
</div>
{{if .IsRMByAdmin}}
<p class="deleted-message">
Deleted by administrator.<br>
Comment ID: #{{.ID}}
</p>
{{end}}
{{if or (not .IsRMByAdmin) .ByMii}}
{{if eq .PostType 1}}
<div class="reply-content-memo">
<img class="reply-memo" src="{{.BodyText}}">
</div>
{{else}}
<div class="reply-content-text">{{.Body}}</div>
{{end}}
{{if .Image}}
{{if eq .AttachmentType 1}}
<a class="screenshot-container">
<audio controls preload="none" src="{{.Image}}"></audio>
</a>
{{else if eq .AttachmentType 2}}
<div class="screenshot-container still-image">
<video controls preload="none" src="{{.Image}}"></video>
</div>
{{else}}
<div class="screenshot-container still-image">
<img src="{{.Image}}">
</div>
{{end}}
{{end}}
{{if .URL}}
{{if eq .URLType 1}}
<div class="screenshot-container video">
<iframe src="https://www.youtube.com/embed/{{.URL}}" width="490" height="276" frameborder="0" allowfullscreen="true"></iframe>
</div>
{{else if eq .URLType 2}}
<div class="screenshot-container video">
<iframe src="https://open.spotify.com/embed/track/{{.URL}}" width="490" height="276" frameborder="0" allow="encrypted-media"></iframe>
</div>
{{else if eq .URLType 3}}
<div class="screenshot-container video audio">
<iframe class="audio" src="https://w.soundcloud.com/player/?url={{.URL}}&auto_play=false&show_artwork=true&color=8000ff" frameborder="0"></iframe>
</div>
{{else}}
<p class="url-link">
<a class="link-confirm" href="{{.URL}}" target="_blank">{{.URL}}</a>
</p>
{{end}}
{{end}}
{{if and .IsSpoiler (not .ByMii)}}
<div class="hidden-content">
<p>This comment contains spoilers.</p>
<button type="button" class="hidden-content-button">View Comment</button>
</div>
{{end}}
{{end}}
{{if not .IsRMByAdmin}}
<div class="reply-meta">
<button type="button" {{if not .CanYeah}} disabled{{end}} class="symbol submit yeah-button
{{if .Yeahed}} yeah-added{{end}}
{{if not .CanYeah}} disabled{{end}}
" data-is-in-reply-list="1" data-feeling="{{.Feeling}}" data-action="/comments/{{.ID}}/yeah" data-url-id="{{.ID}}">
<span class="yeah-button-text">
{{if .Yeahed}}
{{if eq .Feeling 6}}
Unepic
{{else if eq .Feeling 7}}
Unnyeah
{{else if eq .Feeling 8}}
Unyes
{{else if eq .Feeling 9}}
olv.portal.miitoo.delete
{{else}}
Unyeah
{{end}}
{{else}}
{{if eq .Feeling 2}}
Yeah♥
{{else if eq .Feeling 3}}
Yeah!?
{{else if or (eq .Feeling 4) (eq .Feeling 5)}}
Yeah...
{{else if eq .Feeling 6}}
Epic!
{{else if eq .Feeling 7}}
Nyeah~♥
{{else if eq .Feeling 8}}
Yes!
{{else if eq .Feeling 9}}
olv.portal.miitoo.
{{else}}
Yeah!
{{end}}
{{end}}
</span>
</button>
<div class="yeah symbol"><span class="symbol-label">Yeahs</span><span class="yeah-count">{{.YeahCount}}</span></div>
</div>
{{end}}
</div>
</li>

View File

@ -0,0 +1,29 @@
<div class="recent-reply-content">
{{if gt .CommentCount 1}}<div class="recent-reply-read-more-container" tabindex="0">View all comments ({{.CommentCount}})</div>{{end}}
<div tabindex="0" class="recent-reply trigger">
<a href="/users/{{.CommentPreview.CommenterUsername}}" username="{{.CommentPreview.CommenterUsername}}" class="icon-container{{if not .CommentPreview.CommenterHideOnline}} {{if .CommentPreview.CommenterOnline}}online{{else}}offline{{end}}{{end}}
{{if .CommentPreview.CommenterRoleImage}} official-user"><img src="{{.CommentPreview.CommenterRoleImage}}" class="official-tag">{{else}}">{{end}}
<img src="{{.CommentPreview.CommenterIcon}}" class="icon">
</a>
<p class="user-name"><a href="/users/{{.CommentPreview.CommenterUsername}}"{{if .CommentPreview.CommenterColor}} style="color:{{.CommentPreview.CommenterColor}}"{{end}}>{{.CommentPreview.CommenterNickname}}</a></p>
<p class="timestamp-container">
<a class="timestamp" href="/comments/{{.CommentPreview.ID}}">
<span class="update" time="{{.CommentPreview.CreatedAtUnix}}000">{{.CommentPreview.CreatedAt}}</span>
{{if .CommentPreview.EditedAt}}
(Edited <span class="update" time="{{.CommentPreview.EditedAtUnix}}000">{{.CommentPreview.EditedAt}}</span>)
{{end}}
</a>
</p>
<div class="body">
<div class="post-content">
{{if eq .CommentPreview.PostType 1}}
<div class="recent-reply-content-memo">
<img src="{{.CommentPreview.BodyText}}">
</div>
{{else}}
<div class="recent-reply-content-text">{{.CommentPreview.Body}}</div>
{{end}}
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,56 @@
<div class="post scroll {{if .ByMe}}my{{else}}other{{end}}" id="{{.ID}}">
<a href="/users/{{.ByUsername}}" username="{{.ByUsername}}" class="icon-container {{if not .ByHideOnline}}{{if .ByOnline}}online{{else}}offline{{end}}{{end}}{{if .ByRoleImage}} official-user"><img src="{{.ByRoleImage}}" class="official-tag">{{else}}">{{end}}
<img src="{{.ByAvatar}}" class="icon">
</a>
<p class="timestamp-container">
<span class="timestamp{{if .DateUnix}} update" time="{{.DateUnix}}000{{end}}">{{.Date}}</span>
{{if .ByMe}}
<button type="button" class="symbol button edit-button rm-post-button" data-action="/messages/{{.ID}}/delete">
<span class="symbol-label">Delete</span>
</button>
{{end}}
</p>
<div class="post-body">
{{if eq .PostType 1}}
<div class="post-content-memo">
<img class="post-memo" src="{{.BodyText}}">
</div>
{{else}}
<div class="post-content-text">{{.Body}}</div>
{{end}}
{{if .Image}}
{{if eq .AttachmentType 1}}
<div class="screenshot-container">
<audio controls preload="none" src="{{.Image}}"></audio>
</div>
{{else if eq .AttachmentType 2}}
<div class="screenshot-container still-image">
<video controls preload="none" src="{{.Image}}"></video>
</div>
{{else}}
<div class="screenshot-container still-image">
<img src="{{.Image}}">
</div>
{{end}}
{{end}}
{{if .URL}}
{{if eq .URLType 1}}
<div class="screenshot-container video">
<iframe src="https://www.youtube.com/embed/{{.URL}}" width="490" height="276" frameborder="0" allowfullscreen="true"></iframe>
</div>
{{else if eq .URLType 2}}
<div class="screenshot-container video">
<iframe src="https://open.spotify.com/embed/track/{{.URL}}" width="490" height="276" frameborder="0" allow="encrypted-media"></iframe>
</div>
{{else if eq .URLType 3}}
<div class="screenshot-container video audio">
<iframe class="audio" src="https://w.soundcloud.com/player/?url={{.URL}}&auto_play=false&show_artwork=true&color=8000ff" frameborder="0"></iframe>
</div>
{{else}}
<p class="url-link">
<a class="link-confirm" href="{{.URL}}" target="_blank">{{.URL}}</a>
</p>
{{end}}
{{end}}
</div>
</div>

View File

@ -0,0 +1,160 @@
{{if eq .Type 4}}
<div class="post-list-outline no-content repost-error">
<a href="/posts/{{.ID}}">There are more posts in this thread. Click to view...</a>
</div>
{{else if and (eq .Type 3) (not .CommunityName)}}
<div class="post-list-outline no-content repost-error">
<p>The post could not be found.</p>
</div>
{{else if and (eq .Type 3) .IsRMByAdmin}}
<div class="no-content track-error" data-track-error="deleted">
<div>
<p class="deleted-message">
Deleted by administrator.<br>
Post ID: #{{.ID}}
</p>
</div>
</div>
{{else}}
<div id="{{.ID}}"{{if not (eq .Type 3)}}{{if not .IsRMByAdmin}} data-href{{if and .IsSpoiler (not .ByMe)}}-hidden{{end}}="/{{if gt .CommentCount -1}}posts{{else}}comments{{end}}/{{.ID}}"{{end}}{{end}} class="post post-subtype-default{{if not .IsRMByAdmin}} trigger{{end}}{{if .Pinned}} pinned{{end}}{{if and (or .IsSpoiler .IsRMByAdmin) (not .ByMe)}} hidden{{end}}{{if gt .Type 1}} post-list-outline{{end}}{{if eq .Type 3}} repost{{if .Image}} with-image{{end}}{{end}}"{{if not .IsRMByAdmin}} tabindex="0"{{end}}>
{{if not (eq .Type 0)}}
<p class="community-container">
{{if .MigrationImage}}
<a{{if .MigrationURL}} href="{{.MigrationURL}}{{.MigratedID}}"{{end}} class="post-migration-label{{if gt .Type 2}} from-repost{{end}}">
<img src="{{.MigrationImage}}">
</a>
{{end}}
<a{{if not .CommunityRM}} href="/{{if gt .CommentCount -1}}communities{{else}}posts{{end}}/{{.CommunityID}}"{{end}}>
<img src="{{.CommunityIcon}}" class="community-icon">
{{.CommunityName}}
</a>
</p>
{{end}}
<a href="/users/{{.PosterUsername}}" username="{{.PosterUsername}}" class="icon-container {{if not .PosterHideOnline}}{{if .PosterOnline}}online{{else}}offline{{end}}{{end}}
{{if .PosterRoleImage}} official-user"><img src="{{.PosterRoleImage}}" class="official-tag">{{else}}">{{end}}
<img src="{{.PosterIcon}}" class="icon">
</a>
<p class="user-name"><a href="/users/{{.PosterUsername}}"{{if .PosterColor}} style="color:{{.PosterColor}}"{{end}}>{{.PosterNickname}}</a></p>
<p class="timestamp-container">
<span class="spoiler-status{{if .Pinned}} spoiler{{end}}">Pinned ·</span>
{{if .Privacy}}<span class="spoiler-status spoiler">Private ·</span>{{end}}
<span class="spoiler-status{{if .IsSpoiler}} spoiler{{end}}">Spoilers ·</span>
<a class="timestamp"{{if not .IsRMByAdmin}} href="/{{if gt .CommentCount -1}}posts{{else}}comments{{end}}/{{.ID}}"{{end}}>
<span class="update" time="{{.CreatedAtUnix}}000">{{.CreatedAt}}</span>
{{if .EditedAt}}
(Edited <span class="update" time="{{.EditedAtUnix}}000">{{.EditedAt}}</span>)
{{end}}
</a>
</p>
<div class="body post-content">
{{if .IsRMByAdmin}}
<p class="deleted-message">
Deleted by administrator.<br>
{{if eq .CommentCount -1}}Commen{{else}}Pos{{end}}t ID: #{{.ID}}
</p>
{{end}}
{{if or (not .IsRMByAdmin) .ByMe}}
{{if eq .PostType 1}}
<div class="post-content-memo">
<img class="post-memo" src="{{.BodyText}}">
</div>
{{else}}
<div class="post-content-text">{{.Body}}</div>
{{end}}
{{if .Image}}
{{if eq .AttachmentType 1}}
<a class="screenshot-container">
<audio controls preload="none" src="{{.Image}}"></audio>
</a>
{{else if eq .AttachmentType 2}}
<div class="screenshot-container still-image">
<video controls preload="none" src="{{.Image}}"></video>
</div>
{{else}}
<div class="screenshot-container still-image">
<img src="{{.Image}}">
</div>
{{end}}
{{end}}
{{if .URL}}
{{if eq .URLType 1}}
<div class="screenshot-container video">
<iframe src="https://www.youtube.com/embed/{{.URL}}" frameborder="0" allowfullscreen="true"></iframe>
</div>
{{else if eq .URLType 2}}
<div class="screenshot-container video">
<iframe src="https://open.spotify.com/embed/track/{{.URL}}" frameborder="0" allow="encrypted-media"></iframe>
</div>
{{else if eq .URLType 3}}
<div class="screenshot-container video audio">
<iframe class="audio" src="https://w.soundcloud.com/player/?url={{.URL}}&auto_play=false&show_artwork=true&color=8000ff" frameborder="0"></iframe>
</div>
{{else}}
<p class="url-link">
<a class="link-confirm" href="{{.URL}}" target="_blank">{{.URL}}</a>
</p>
{{end}}
{{end}}
{{if and .IsSpoiler (not .ByMe)}}
<div class="hidden-content">
<p>This post contains spoilers.</p>
<button type="button" class="hidden-content-button">View Post</button>
</div>
{{end}}
{{if eq .PostType 2}}
{{template "poll.html" .Poll}}
{{end}}
{{if .RepostID}}
{{template "render_post.html" .Repost}}
{{end}}
{{if and (lt .Type 3) (not .IsRMByAdmin)}}
<div class="post-meta">
<button type="button" {{if not .CanYeah}}disabled{{end}} class="symbol submit yeah-button
{{if not .CanYeah}} disabled{{end}}
{{if .Yeahed}} yeah-added{{end}}
" data-feeling="{{.Feeling}}" data-action="/posts/{{.ID}}/yeah" data-url-id="{{.ID}}">
<span class="yeah-button-text">
{{if .Yeahed}}
{{if eq .Feeling 6}}
Unepic
{{else if eq .Feeling 7}}
Unnyeah
{{else if eq .Feeling 8}}
Unyes
{{else if eq .Feeling 9}}
olv.portal.miitoo.delete
{{else}}
Unyeah
{{end}}
{{else}}
{{if eq .Feeling 2}}
Yeah♥
{{else if eq .Feeling 3}}
Yeah!?
{{else if or (eq .Feeling 4) (eq .Feeling 5)}}
Yeah...
{{else if eq .Feeling 6}}
Epic!
{{else if eq .Feeling 7}}
Nyeah~♥
{{else if eq .Feeling 8}}
Yes!
{{else if eq .Feeling 9}}
olv.portal.miitoo.
{{else}}
Yeah!
{{end}}
{{end}}
</span>
</button>
<div class="yeah symbol"><span class="symbol-label">Yeahs</span><span class="yeah-count">{{.YeahCount}}</span></div>
{{if gt .CommentCount -1}}<div class="reply symbol"><span class="symbol-label">Comments</span><span class="reply-count">{{.CommentCount}}</span></div>{{end}}
</div>
{{if .CommentPreview.BodyText}}
{{template "render_comment_preview.html" .}}
{{end}}
{{end}}
{{end}}
</div>
</div>
{{end}}

View File

@ -0,0 +1,144 @@
<div id="{{.ID}}"{{if not .IsRMByAdmin}} data-href{{if and .IsSpoiler (not .ByMe)}}-hidden{{end}}="/posts/{{.ID}}"{{end}} class="post{{if not .IsRMByAdmin}} trigger{{end}}{{if .Image}} with-image{{end}}{{if and (or .IsSpoiler .IsRMByAdmin) (not .ByMe)}} hidden{{end}}"{{if not .IsRMByAdmin}} tabindex="0"{{end}}>
<p class="community-container">
{{if .MigrationImage}}
<a{{if .MigrationURL}} href="{{.MigrationURL}}{{.MigratedID}}"{{end}} class="post-migration-label">
<img src="{{.MigrationImage}}">
</a>
{{end}}
<a{{if not .CommunityRM}} href="/communities/{{.CommunityID}}"{{end}}>
<img src="{{.CommunityIcon}}" class="community-icon">
{{.CommunityName}}
</a>
</p>
<div class="body">
<div class="post-content">
<a href="/users/{{.PosterUsername}}" username="{{.PosterUsername}}" class="icon-container
{{if not .PosterHideOnline}}
{{if .PosterOnline}}
online
{{else}}
offline
{{end}}
{{end}}
{{if .PosterRoleImage}} official-user"><img src="{{.PosterRoleImage}}" class="official-tag">{{else}}">{{end}}
<img src="{{.PosterIcon}}" class="icon">
</a>
<p class="user-name"><a href="/users/{{.PosterUsername}}"{{if .PosterColor}} style="color:{{.PosterColor}}"{{end}}>{{.PosterNickname}}</a></p>
<p class="timestamp-container">
{{if .Pinned}}
<span class="spoiler">Pinned</span> ·
{{end}}
{{if .Privacy}}
<span class="spoiler">Private</span> ·
{{end}}
{{if .IsSpoiler}}
<span class="spoiler">Spoilers</span> ·
{{end}}
<a class="timestamp"{{if not .IsRMByAdmin}} href="/posts/{{.ID}}"{{end}}>
<span class="update" time="{{.CreatedAtUnix}}000">{{.CreatedAt}}</span>
{{if .EditedAt}}
(Edited <span class="update" time="{{.EditedAtUnix}}000">{{.EditedAt}}</span>)
{{end}}
</a>
</p>
{{if .IsRMByAdmin}}
<p class="deleted-message">
Deleted by administrator.<br>
Post ID: #{{.ID}}
</p>
{{end}}
{{if or (not .IsRMByAdmin) .ByMe}}
{{if .Image}}
{{if eq .AttachmentType 1}}
<a class="screenshot-container">
<audio controls preload="none" src="{{.Image}}"></audio>
</a>
{{else if eq .AttachmentType 2}}
<div class="screenshot-container still-image">
<video controls preload="none" src="{{.Image}}"></video>
</div>
{{else}}
<div class="screenshot-container still-image">
<img src="{{.Image}}">
</div>
{{end}}
{{end}}
{{if .URL}}
{{if eq .URLType 1}}
<a href="/posts/{{.ID}}" class="screenshot-container video">
<img height="48" src="https://i.ytimg.com/vi/{{.URL}}/default.jpg">
</a>
{{else}}
<p class="url-link">
<a class="link-confirm" href="{{if eq .URLType 2}}https://open.spotify.com/track/{{.URL}}{{else}}{{.URL}}{{end}}" target="_blank">{{if eq .URLType 2}}https://open.spotify.com/track/{{end}}{{.URL}}</a>
</p>
{{end}}
{{end}}
{{if eq .PostType 1}}
<div class="post-content-memo">
<img class="post-memo" src="{{.BodyText}}">
</div>
{{else}}
<div class="post-content-text">{{.Body}}</div>
{{end}}
{{if and .IsSpoiler (not .ByMe)}}
<div class="hidden-content">
<p>This post contains spoilers.
<button type="button" class="hidden-content-button">View Post</button>
</p>
</div>
{{end}}
{{if eq .PostType 2}}
{{template "poll.html" .Poll}}
{{end}}
{{if .RepostID}}
{{template "render_post.html" .Repost}}
{{end}}
{{if not .IsRMByAdmin}}
<div class="post-meta">
<button type="button" {{if not .CanYeah}} disabled{{end}} class="symbol submit yeah-button
{{if .Yeahed}} yeah-added{{end}}
{{if not .CanYeah}} disabled{{end}}
" data-feeling="{{.Feeling}}" data-action="/posts/{{.ID}}/yeah" data-community-id data-url-id="{{.ID}}">
<span class="yeah-button-text">
{{if .Yeahed}}
{{if eq .Feeling 6}}
Unepic
{{else if eq .Feeling 7}}
Unnyeah
{{else if eq .Feeling 8}}
Unyes
{{else if eq .Feeling 9}}
olv.portal.miitoo.delete
{{else}}
Unyeah
{{end}}
{{else}}
{{if eq .Feeling 2}}
Yeah♥
{{else if eq .Feeling 3}}
Yeah!?
{{else if or (eq .Feeling 4) (eq .Feeling 5)}}
Yeah...
{{else if eq .Feeling 6}}
Epic!
{{else if eq .Feeling 7}}
Nyeah~♥
{{else if eq .Feeling 8}}
Yes!
{{else if eq .Feeling 9}}
olv.portal.miitoo.
{{else}}
Yeah!
{{end}}
{{end}}
</span>
</button>
<div class="yeah symbol"><span class="symbol-label">Yeahs</span><span class="yeah-count">{{.YeahCount}}</span></div>
<div class="reply symbol"><span class="symbol-label">Comments</span><span class="reply-count">{{.CommentCount}}</span></div>
</div>
{{end}}
{{end}}
</div>
</div>
</div>

View File

@ -0,0 +1,4 @@
<a href="/users/{{.Username}}" id="{{.ID}}" class="post-permalink-feeling-icon">
{{if .Role}}<img src="{{.Role}}" class="official-tag">{{end}}
<img src="{{.Avatar}}" class="user-icon">
</a>

15
views/error.html Normal file
View File

@ -0,0 +1,15 @@
{{if .Pjax}}
{{template "header.html" .}}
{{else}}
<title>{{.Title}}</title>
<meta property="og:description" content="I AM ERROR. haha get it funny gamer reference that only true gamers will understand">
{{end}}
<div id="main-body">
<div class="no-content">
<p>{{.Error}}</p><br>
<img src="https://upload.wikimedia.org/wikipedia/en/c/cc/Wojak_cropped.jpg">
</div>
</div>
{{if .Pjax}}
{{template "footer.html"}}
{{end}}

View File

@ -0,0 +1,74 @@
{{if .Pjax}}
{{template "header.html" .}}
{{else}}
<title>{{.Title}}</title>
{{end}}
<div id="main-body">
{{template "general_sidebar.html" .}}
<div class="main-column messages">
<div class="post-list-outline">
<h2 class="label">Friend Requests</h2>
<div id="notification-tab-container" class="tab-container">
<div class="tab2">
<a class="tab-icon-my-news{{if .Notify}} notify{{end}}" href="/notifications">
<span class="symbol nf"></span>
<span>Updates</span>
</a>
<a class="tab-icon-my-news selected" href="/notifications/friend_requests">
<span class="symbol fr"></span>
<span>Friend Requests</span>
</a>
</div>
</div>
<div class="list news-list">
{{if .FriendRequests}}
{{$username := .CurrentUser.Username}}
{{range $request := .FriendRequests}}
<div class="dialog none" data-modal-types="accept-friend-request" data-screen-name="{{$request.ByNickname}}" data-reject-action="/users/{{$request.ByUsername}}/friend_reject" data-action="/users/{{$request.ByUsername}}/friend_accept" uuid="{{$request.ID}}">
<div class="dialog-inner">
<div class="window">
<h1 class="window-title">Friend Request from {{$request.ByNickname}} at <span class="update" time="{{$request.CreatedAtUnix}}000">{{$request.CreatedAt}}</span></h1>
<div class="window-body">
<div id="sidebar-profile-body">
<div username="{{$request.ByUsername}}" class="icon-container {{if not $request.ByHideOnline}}{{if $request.ByOnline}}online{{else}}offline{{end}}{{end}}{{if $request.ByRoleImage}} official-user"><img src="{{$request.ByRoleImage}}" class="official-tag">{{else}}">{{end}}
<a href="/users/{{$request.ByUsername}}"><img src="{{$request.ByAvatar}}" alt="{{$request.ByNickname}}" class="icon"></a>
</div>
{{if $request.ByRoleOrganization}}<p class="user-organization">{{$request.ByRoleOrganization}}</p>{{end}}
<a href="/users/{{$request.ByUsername}}" class="nick-name"{{if $request.ByColor}} style="color:{{$request.ByColor}}"{{end}}>{{$request.ByNickname}}</a>
<p class="id-name">{{$request.ByUsername}}</p>
</div>
{{if $request.Message}}<pre>{{$request.Message}}</pre>{{end}}
<p class="window-body-content">Accept {{$request.ByNickname}}'s friend request?</p>
<div class="form-buttons three">
<button class="olv-modal-close-button gray-button" data-event-type="cancel" type="button">Cancel</button>
<button class="cancel-button gray-button" type="button">Reject</button>
<button class="ok-button post-button black-button" data-event-type="ok" type="button">Accept</button>
</div>
</div>
</div>
</div>
</div>
<div class="news-list-content{{if not $request.Read}} notify{{end}} trigger" tabindex="0" id="{{$request.ID}}" data-href="/users/{{$request.ByUsername}}">
<div username="{{$request.ByUsername}}" class="icon-container {{if $request.ByOnline}}online{{else}}offline{{end}}{{if $request.ByRoleImage}} official-user"><img src="{{$request.ByRoleImage}}" class="official-tag">{{else}}">{{end}}
<a href="/users/{{$request.ByUsername}}"><img src="{{$request.ByAvatar}}" alt="{{$request.ByNickname}}" class="icon"></a>
</div>
<div class="body">
<a href="/users/{{$request.ByUsername}}" class="nick-name">{{$request.ByNickname}}</a>
<br>
<span class="timestamp update" time="{{$request.CreatedAtUnix}}000">{{$request.Date}}</span>
<button class="button received-request-button" type="button">View friend request</button>
</div>
</div>
{{end}}
{{else}}
<div class="no-content">
<p>You have no friend requests.</p>
</div>
{{end}}
</div>
</div>
</div>
</div>
{{if .Pjax}}
{{template "footer.html"}}
{{end}}

29
views/help/rules.html Normal file
View File

@ -0,0 +1,29 @@
{{if .Pjax}}
{{template "header.html" .}}
<meta property="og:description" content="Before joining Indigo, it's highly recommended that you read these rules so as to not cause any issues.">
{{else}}
<title>rules</title>
{{end}}
<div id="main-body" class="profile-top">
{{template "general_sidebar.html" .}}
<div class="main-column" id="help">
<div class="post-list-outline">
<h2 class="label">about SOP.epic</h2>
<div id="guide" class="help-content">
<h2>rules</h2>
<p>
1: there are hardly any except please dont post porn or gore or your the location of your uncles meth lab<br>
2: dont post malware or a site that easily can give you malware in the piracy cabal network (exception is if you explicitly state you need an adblock or something)
</p>
<h2>this site looks like ass! the background hurts my eyes!</h2>
<p>you can change it in the profile settings!!!!</p>
<h2>this place seems vaguely familiar...</h2>
<p>running heavily modified <a href= "https://github.com/PF2M/Indigo">Indigo</a></p>
<img src="/assets/img/dancingjesus.gif">
</div>
</div>
</div>
</div>
{{if .Pjax}}
{{template "footer.html"}}
{{end}}

35
views/index.html Normal file
View File

@ -0,0 +1,35 @@
{{if .Pjax}}
{{template "header.html" .}}
<meta property="og:description" content="SOP.epic is a feature packed twitter alternative with no paywalling of basic features and easily customizable themes!">
{{else}}
<title>{{.Title}} - sop.epic</title>
{{end}}
<style>
h1 {color:black;}
p {color:black;}
</style>
<div id="main-body">
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<h1>Welcome to SOP.epic!</h1><br>
<p>SOP.epic (pronounced ess-oh-pee) is a feature packed twitter alternative with no paywalling of basic features and easily customizable themes!<p>
<img src="assets/img/weedchan.png"><br>
<p>awesome art:<br><br>
<img src="assets/img/art/lg_16150364_img_20221203_065249.png" width="430" height="330"> by rem
<img src="assets/img/art/ketamine.png" width="430" height="384"> by minitt
</div>
{{if .Pjax}}
{{template "footer.html"}}
{{end}}

47
views/messages.html Normal file
View File

@ -0,0 +1,47 @@
{{if .Pjax}}
{{template "header.html" .}}
{{else}}
<title>{{.Title}}</title>
{{end}}
<div id="main-body">
{{template "general_sidebar.html" .}}
<div class="main-column messages">
<div class="post-list-outline">
<h2 class="label">Messages<a href="/conversations/create"><button class="button msg-update">Create Group</button></a></h2>
<div class="list">
<ul class="list-content-with-icon-and-text arrow-list"{{if .Conversations}} data-next-page-url="/messages?offset={{.Offset}}&offset_time={{.OffsetTime}}"{{end}}>
{{if .Conversations}}
{{$user_id := .CurrentUser.ID}}
{{range $conversation := .Conversations}}
<li class="trigger{{if (and (not $conversation.Read) (not (eq $conversation.CreatedBy $user_id)))}} notify{{end}}" data-href="/{{if not (eq $conversation.Target 0)}}messages{{else}}conversations{{end}}/{{$conversation.Username}}">
<a href="/{{if eq $conversation.Target 0}}conversations{{else}}users{{end}}/{{$conversation.Username}}" username="{{$conversation.Username}}" class="icon-container {{if not $conversation.HideOnline}}{{if $conversation.Online}}online{{else}}offline{{end}}{{end}}
{{if $conversation.RoleImage}} official-user"><img src="{{$conversation.RoleImage}}" class="official-tag">{{else}}">{{end}}
<img src="{{$conversation.Icon}}" class="icon">
</a>
<div class="body">
<p class="title">
<span class="nick-name">
<a href="/{{if eq $conversation.Target 0}}conversations{{else}}users{{end}}/{{$conversation.Username}}"{{if $conversation.Color}} style="color:{{$conversation.Color}}"{{end}}>{{$conversation.Nickname}}</a>
</span>
{{if not (eq $conversation.Target 0)}}<span class="id-name">{{$conversation.Username}}</span>{{end}}
</p>
{{if $conversation.Date}}<span class="timestamp update" time="{{$conversation.DateUnix}}000">{{$conversation.Date}}</span>{{end}}
<p class="text {{if eq $conversation.CreatedBy $user_id}}my{{else}}other{{end}}{{if and ($conversation.BodyText) (eq $conversation.PostType 0)}}">{{$conversation.BodyText}}{{else if eq $conversation.PostType 1}} type-memo">(handwritten){{else if $conversation.Image}} type-memo">(attachment){{else}} placeholder">You haven't exchanged messages with this {{if eq $conversation.Target 0}}group{{else}}user{{end}} yet.{{end}}</p>
</div>
</li>
{{end}}
{{else}}
{{if eq .Offset 20}}
<div class="no-content">
<p>No messages.</p>
</div>
{{end}}
{{end}}
</ul>
</div>
</div>
</div>
</div>
{{if .Pjax}}
{{template "footer.html"}}
{{end}}

73
views/notifications.html Normal file
View File

@ -0,0 +1,73 @@
{{if .Pjax}}
{{template "header.html" .}}
{{else}}
<title>{{.Title}}</title>
{{end}}
<div id="main-body">
{{template "general_sidebar.html" .}}
<div class="main-column messages">
<div class="post-list-outline">
<h2 class="label">My notifications</h2>
<div id="notification-tab-container" class="tab-container">
<div class="tab2">
<a class="tab-icon-my-news selected" href="/notifications">
<span class="symbol nf"></span>
<span>Updates</span>
</a>
<a class="tab-icon-my-news{{if .Notify}} notify{{end}}" href="/notifications/friend_requests">
<span class="symbol fr"></span>
<span>Friend Requests</span>
</a>
</div>
</div>
<div class="list news-list">
{{if .Notifs}}
{{$username := .CurrentUser.Username}}
{{range $notif := .Notifs}}
<div class="news-list-content{{if not $notif.Read}} notify{{end}} trigger" tabindex="0" id="{{$notif.ID}}" data-href="
{{if or (or (eq $notif.Type 0) (eq $notif.Type 2)) (or (eq $notif.Type 3) (eq $notif.Type 7))}}
/posts/{{$notif.Post.Int64}}
{{else if eq $notif.Type 1}}
/comments/{{$notif.Post.Int64}}
{{else if eq $notif.Type 7}}
/news/fuck
{{else}}
/users/{{$username}}/followers
{{end}}
">
<a href="/users/{{$notif.ByUsername}}" username="{{$notif.ByUsername}}" class="icon-container {{if not $notif.ByHideOnline}}{{if $notif.ByOnline}}online{{else}}offline{{end}}{{end}}
{{if $notif.ByRoleImage}} official-user"><img src="{{$notif.ByRoleImage}}" class="official-tag">{{else}}">{{end}}
<img src="{{$notif.ByAvatar}}" class="icon">
</a>
<div class="body">
<!-- i'm sorry mom -->
{{if eq $notif.Type 4}}Followed by {{end}}<a href="/users/{{$notif.ByUsername}}" class="nick-name"{{if $notif.ByColor}} style="color:{{$notif.ByColor}}"{{end}}>{{$notif.ByNickname}}</a>{{if $notif.MergedCount}}{{if eq $notif.MergedCount 1}} and <a href="/users/{{index $notif.MergedUsername 0}}" class="nick-name"{{if index $notif.MergedColor 0}} style="color:{{index $notif.MergedColor 0}}"{{end}}>{{index $notif.MergedNickname 0}}</a>{{else if eq $notif.MergedCount 2}}, <a href="/users/{{index $notif.MergedUsername 0}}" class="nick-name"{{if index $notif.MergedColor 0}} style="color:{{index $notif.MergedColor 0}}"{{end}}>{{index $notif.MergedNickname 0}}</a>, and <a href="/users/{{index $notif.MergedUsername 1}}" class="nick-name"{{if index $notif.MergedColor 1}} style="color:{{index $notif.MergedColor 1}}"{{end}}>{{index $notif.MergedNickname 1}}</a>{{else if eq $notif.MergedCount 3}}, <a href="/users/{{index $notif.MergedUsername 0}}" class="nick-name"{{if (index $notif.MergedColor 0)}} style="color:{{index $notif.MergedColor 0}}"{{end}}>{{index $notif.MergedNickname 0}}</a>, <a href="/users/{{index $notif.MergedUsername 1}}" class="nick-name"{{if index $notif.MergedColor 1}} style="color:{{index $notif.MergedColor 1}}"{{end}}>{{index $notif.MergedNickname 1}}</a>, and <a href="/users/{{index $notif.MergedUsername 2}}" class="nick-name"{{if index $notif.MergedColor 2}} style="color:{{index $notif.MergedColor 2}}"{{end}}>{{index $notif.MergedNickname 2}}</a>{{else if eq $notif.MergedCount 4}}, <a href="/users/{{index $notif.MergedUsername 0}}" class="nick-name"{{if index $notif.MergedColor 0}} style="color:{{index $notif.MergedColor 0}}"{{end}}>{{index $notif.MergedNickname 0}}</a>, <a href="/users/{{index $notif.MergedUsername 1}}" class="nick-name"{{if index $notif.MergedColor 1}} style="color:{{index $notif.MergedColor 1}}"{{end}}>{{index $notif.MergedNickname 1}}</a>, <a href="/users/{{index $notif.MergedUsername 2}}" class="nick-name"{{if index $notif.MergedColor 2}} style="color:{{index $notif.MergedColor 2}}"{{end}}>{{index $notif.MergedNickname 2}}</a>, and 1 other{{else}}, <a href="/users/{{index $notif.MergedUsername 0}}" class="nick-name"{{if index $notif.MergedColor 0}} style="color:{{index $notif.MergedColor 0}}"{{end}}>{{index $notif.MergedNickname 0}}</a>, <a href="/users/{{index $notif.MergedUsername 1}}" class="nick-name"{{if index $notif.MergedColor 1}} style="color:{{index $notif.MergedColor 1}}"{{end}}>{{index $notif.MergedNickname 1}}</a>, <a href="/users/{{index $notif.MergedUsername 2}}" class="nick-name"{{if index $notif.MergedColor 2}} style="color:{{index $notif.MergedColor 2}}"{{end}}>{{index $notif.MergedNickname 2}}</a>, and {{$notif.MergedOthers}} others{{end}}{{end}}
{{if eq $notif.Type 0}}
gave <a href="/posts/{{$notif.Post.Int64}}" class="link">your post&nbsp;({{$notif.PostText}})</a> a Yeah.
{{else if eq $notif.Type 1}}
gave <a href="/comments/{{$notif.Post.Int64}}" class="link">your comment&nbsp;({{$notif.PostText}})</a> a Yeah.
{{else if eq $notif.Type 2}}
commented on <a href="/posts/{{$notif.Post.Int64}}" class="link">your post&nbsp;({{$notif.PostText}})</a>.
{{else if eq $notif.Type 3}}
commented on <a href="/posts/{{$notif.Post.Int64}}" class="link">{{$notif.ByNickname}}'s post&nbsp;({{$notif.PostText}})</a>.
{{else if eq $notif.Type 7}}
reposted <a href="/posts/{{$notif.Post.Int64}}" class="link">your post&nbsp;({{$notif.PostText}})</a>.
{{else if eq $notif.Type 8}}
<br>You have received a notification from the Indigo Administration.
{{end}}
<span class="timestamp update" time="{{$notif.DateUnix}}000">{{$notif.Date}}</span>
</div>
</div>
{{end}}
{{else}}
<div class="no-content">
<p>No notifications.</p>
</div>
{{end}}
</div>
</div>
</div>
</div>
{{if .Pjax}}
{{template "footer.html"}}
{{end}}

352
views/post.html Normal file
View File

@ -0,0 +1,352 @@
{{if .Pjax}}
{{template "header.html" .}}
<meta property="og:profile:username" content="{{.Post.PosterUsername}}">
<meta property="og:description" content="{{if .Post.Body}}{{.Post.Body}}{{else}}View {{.Post.PosterNickname}}'s post on Indigo.{{end}}">
{{if .Post.Image}}<meta property="og:{{if eq .Post.AttachmentType 1}}audio{{else if eq .Post.AttachmentType 2}}video{{else}}image{{end}}" content="{{.Post.Image}}">{{end}}
{{else}}
<title>{{.Title}} - sop.epic</title>
{{end}}
<div id="main-body">
<div class="main-column replyform-bottom">
<div class="post-list-outline">
<section id="post-content" class="post post-subtype-default">
<header class="community-container">
{{if .Post.MigrationImage}}
<a{{if .Post.MigrationURL}} href="{{.Post.MigrationURL}}{{.Post.MigratedID}}"{{end}} class="post-migration-label">
<img src="{{.Post.MigrationImage}}">
</a>
{{end}}
<h1 class="community-container-heading">
<a{{if not .Community.RM}} href="/communities/{{.Community.ID}}"{{end}}>
<img src="{{.Community.Icon}}" class="community-icon">
{{.Community.Title}}
</a>
</h1>
</header>
{{if and .CurrentUser.Username (not .Post.IsRMByAdmin)}}
<div class="edit-buttons-content">
{{if or (eq .Post.CreatedBy .CurrentUser.ID) (gt .CurrentUser.Level 0)}}<button type="button" class="symbol button edit-button rm-post-button" data-action="/posts/{{.Post.ID}}/delete"><span class="symbol-label">Delete</span></button>{{end}}
{{if and (eq .Post.CreatedBy .CurrentUser.ID) (not (eq .Post.PostType 1))}}<button type="button" class="symbol button edit-button edit-post-button"><span class="symbol-label">Edit</span></button>{{end}}
{{if and (.Post.Image) (eq .Post.AttachmentType 0)}}<button type="button" class="symbol button edit-button profile-post-button{{if .IsFavorite}} done{{end}}" data-action="/posts/{{.Post.ID}}/{{if .IsFavorite}}un{{end}}favorite"><span class="symbol-label">Set as Favorite Post</span></button>{{end}}
<button type="button" class="symbol button edit-button repost-button" post="{{.Post.ID}}"><span class="symbol-label">Repost</span></button>
</div>
{{if not (eq .Post.CreatedBy .CurrentUser.ID)}}<div class="report-buttons-content" style="float:right"><button type="button" class="report-button" data-modal-open="#report-violation-page" data-screen-name="{{.Post.PosterNickname}}" data-support-text="#{{.Post.ID}}" data-action="/posts/{{.Post.ID}}/violations" data-can-report-spoiler="{{if .Post.IsSpoiler}}0{{else}}1{{end}}" data-track-action="openReportModal" data-track-category="reportViolation">Report Violation</button></div>{{end}}
{{end}}
<div class="user-content">
<a href="/users/{{.Post.PosterUsername}}" username="{{.Post.PosterUsername}}" class="icon-container
{{if not .Post.PosterHideOnline}}
{{if .Post.PosterOnline}}
online
{{else}}
offline
{{end}}
{{end}}
{{if .Post.PosterRoleImage}} official-user"><img src="{{.Post.PosterRoleImage}}" class="official-tag">{{else}}">{{end}}
<img src="{{.Post.PosterIcon}}" class="icon">
</a>
<div class="user-name-content">
{{if .Post.PosterRoleOrganization}}<p class="user-organization">{{.Post.PosterRoleOrganization}}</p>{{end}}
<p class="user-name">
<a href="/users/{{.Post.PosterUsername}}"{{if .Post.PosterColor}} style="color:{{.Post.PosterColor}}"{{end}}>{{.Post.PosterNickname}}</a>
<span class="user-id">{{.Post.PosterUsername}}</span>
</p>
<p class="timestamp-container">
<span class="timestamp">
<span class="update" time="{{.Post.CreatedAtUnix}}000">{{.Post.CreatedAt}}</span>
{{if .Post.EditedAt}}
(Edited <span class="update" time="{{.Post.EditedAtUnix}}000">{{.Post.EditedAt}}</span>)
{{end}}
</span>
<span class="spoiler-status{{if .Post.Pinned}} spoiler{{end}}">· Pinned</span>
{{if .Post.Privacy}}
<span class="spoiler-status spoiler">· Private ({{if eq .Post.Privacy 1}}Friends, Following and Followers{{else if eq .Post.Privacy 2}}Friends and Following{{else if eq .Post.Privacy 3}}Friends and Followers{{else if eq .Post.Privacy 4}}Friends Only{{else if eq .Post.Privacy 5}}Followers and Following{{else if eq .Post.Privacy 6}}Followers Only{{else if eq .Post.Privacy 7}}Following Only{{else if eq .Post.Privacy 8}}Admins Only{{else}}Only Me{{end}})</span>
{{end}}
<span class="spoiler-status{{if .Post.IsSpoiler}} spoiler{{end}}">· Spoilers</span>
</p>
</div>
</div>
<div class="body">
{{if eq .Post.CreatedBy .CurrentUser.ID}}
<div id="post-edit" class="none">
<form data-action="/posts/{{.Post.ID}}/edit" id="edit-form" method="post">
<div class="feeling-selector js-feeling-selector test-feeling-selector"><label class="symbol feeling-button feeling-button-normal checked"><input type="radio" name="feeling_id" value="0"{{if eq .Post.Feeling 0}} checked{{end}}><span class="symbol-label">normal</span></label><label class="symbol feeling-button feeling-button-happy"><input type="radio" name="feeling_id" value="1"{{if eq .Post.Feeling 1}} checked{{end}}><span class="symbol-label">happy</span></label><label class="symbol feeling-button feeling-button-like"><input type="radio" name="feeling_id" value="2"{{if eq .Post.Feeling 2}} checked{{end}}><span class="symbol-label">like</span></label><label class="symbol feeling-button feeling-button-surprised"><input type="radio" name="feeling_id" value="3"{{if eq .Post.Feeling 3}} checked{{end}}><span class="symbol-label">surprised</span></label><label class="symbol feeling-button feeling-button-frustrated"><input type="radio" name="feeling_id" value="4"{{if eq .Post.Feeling 4}} checked{{end}}><span class="symbol-label">frustrated</span></label><label class="symbol feeling-button feeling-button-puzzled"><input type="radio" name="feeling_id" value="5"{{if eq .Post.Feeling 5}} checked{{end}}><span class="symbol-label">puzzled</span></label></div>
<div class="textarea-container">
<textarea name="body" class="textarea-text textarea " maxlength="2000" placeholder="Edit your post." data-required>{{.Post.BodyText}}</textarea>
</div>
<div class="post-form-footer-options">
<label class="spoiler-button symbol"><input id="is_spoiler" name="is_spoiler" type="checkbox" value="1"{{if .Post.IsSpoiler}} checked{{end}}>Spoilers</label>
</div>
<div class="post-form-privacy">
<p>Who should be able to see this post?</p>
<select class="post-form-privacy-select" name="privacy">
<option value="0"{{if eq .Post.Privacy 0}} selected{{end}}>Everyone</option>
<option value="1"{{if eq .Post.Privacy 1}} selected{{end}}>Friends, Following and Followers</option>
<option value="2"{{if eq .Post.Privacy 2}} selected{{end}}>Friends and Following</option>
<option value="3"{{if eq .Post.Privacy 3}} selected{{end}}>Friends and Followers</option>
<option value="4"{{if eq .Post.Privacy 4}} selected{{end}}>Friends Only</option>
<option value="5"{{if eq .Post.Privacy 5}} selected{{end}}>Followers and Following</option>
<option value="6"{{if eq .Post.Privacy 6}} selected{{end}}>Followers Only</option>
<option value="7"{{if eq .Post.Privacy 7}} selected{{end}}>Following Only</option>
<option value="8"{{if eq .Post.Privacy 8}} selected{{end}}>Admins Only</option>
<option value="9"{{if eq .Post.Privacy 9}} selected{{end}}>Only Me</option>
</select>
</div>
<div class="form-buttons">
<button type="button" class="cancel-button gray-button">Cancel</button>
<button type="submit" class="post-button black-button">Submit</button>
</div>
</form>
</div>
{{else if and (.CurrentUser.Username) (not (eq .Post.PostType 1))}}
<div id="report-violation-page" class="dialog none" data-modal-types="report report-violation" data-is-template="1">
<div class="dialog-inner">
<div class="window">
<h1 class="window-title">report chuddery</h1>
<div class="window-body">
<p class="description">you are about to report to the owner of SOP.epic on regards of breaking the rules</p>
<form method="post" action="/posts/{{.Post.ID}}/violations">
<input type="hidden" name="csrfmiddlewaretoken" value="{{.CurrentUser.CSRFToken}}">
<p class="select-button-label">Violation Type: </p>
<select name="type" class="cannot-report-spoiler">
<option selected value>Please make a selection.</option>
{{range $index, $reason := .Reasons}}
{{if $reason.Enabled}}
<option value="{{$index}}"{{if $reason.BodyRequired}} data-body-required="1"{{end}}>{{$reason.Name}}</option>
{{end}}
{{end}}
</select>
<select name="type" class="can-report-spoiler">
<option selected value>Please make a selection.</option>
<option value="spoiler" data-body-required="1" data-track-action="Spoiler">Spoiler</option>
{{range $index, $reason := .Reasons}}
{{if $reason.Enabled}}
<option value="{{$index}}"{{if $reason.BodyRequired}} data-body-required="1"{{end}}>{{$reason.Name}}</option>
{{end}}
{{end}}
</select>
<textarea name="body" class="textarea" maxlength="100" data-placeholder="Enter a reason for the report."></textarea>
<p class="post-id">Post ID: #{{.Post.ID}}</p>
<div class="form-buttons">
<input type="button" class="olv-modal-close-button gray-button" value="Cancel">
<input type="submit" class="post-button black-button" value="Submit Report" data-url-id="{{.Post.ID}}" data-track-action="openReportModal">
</div>
</form>
</div>
</div>
</div>
</div>
{{end}}
<div id="the-post">
{{if .Post.IsRMByAdmin}}
<p class="deleted-message">
Deleted by administrator.<br>
Post ID: #{{.Post.ID}}
</p>
{{end}}
{{if or (not .Post.IsRMByAdmin) (eq .Post.CreatedBy .CurrentUser.ID)}}
{{if eq .Post.PostType 1}}
<div class="post-content-memo">
<img class="post-memo" src="{{.Post.BodyText}}">
</div>
{{else}}
<div class="post-content-text">{{.Post.Body}}</div>
{{end}}
{{if .Post.Image}}
{{if eq .Post.AttachmentType 1}}
<div class="screenshot-container">
<audio controls preload="none" src="{{.Post.Image}}"></audio>
</div>
{{else if eq .Post.AttachmentType 2}}
<div class="screenshot-container video">
<video controls preload="none" src="{{.Post.Image}}"></video>
</div>
{{else}}
<div class="screenshot-container still-image">
<img src="{{.Post.Image}}">
</div>
{{end}}
{{end}}
{{if .Post.URL}}
{{if eq .Post.URLType 1}}
<div class="screenshot-container video">
<iframe width="490" height="276" src="https://www.youtube.com/embed/{{.Post.URL}}" frameborder="0" allowfullscreen="true"></iframe>
</div>
{{else if eq .Post.URLType 2}}
<div class="screenshot-container video">
<iframe width="490" height="276" src="https://open.spotify.com/embed/track/{{.Post.URL}}" frameborder="0" allow="encrypted-media"></iframe>
</div>
{{else if eq .Post.URLType 3}}
<div class="screenshot-container video audio">
<iframe class="audio" src="https://w.soundcloud.com/player/?url={{.Post.URL}}&auto_play=false&show_artwork=true&color=8000ff" frameborder="0"></iframe>
</div>
{{else}}
<p class="url-link">
<a class="link-confirm" href="{{.Post.URL}}" target="_blank">{{.Post.URL}}</a>
</p>
{{end}}
{{end}}
{{if eq .Post.PostType 2}}
{{template "poll.html" .Post.Poll}}
{{end}}
{{if .Post.RepostID}}
{{template "render_post.html" .Post.Repost}}
{{end}}
{{end}}
{{if not .Post.IsRMByAdmin}}
<div class="post-meta">
<button type="button" {{if (or (eq .Post.CreatedBy .CurrentUser.ID) (not .CurrentUser.Username))}}disabled{{end}} class="symbol submit yeah-button
{{if .Post.Yeahed}} yeah-added{{end}}
{{if not .Post.CanYeah}} disabled{{end}}
" data-feeling="{{.Post.Feeling}}" data-action="/posts/{{.Post.ID}}/yeah" data-url-id="{{.Post.ID}}">
<span class="yeah-button-text">
{{if .Post.Yeahed}}
{{if eq .Post.Feeling 6}}
Unlike
{{else if eq .Post.Feeling 7}}
Unnyeah
{{else if eq .Post.Feeling 8}}
Unyes
{{else if eq .Post.Feeling 9}}
olv.portal.miitoo.delete
{{else}}
Unyeah
{{end}}
{{else}}
{{if eq .Post.Feeling 2}}
Yeah♥
{{else if eq .Post.Feeling 3}}
Yeah!?
{{else if or (eq .Post.Feeling 4) (eq .Post.Feeling 5)}}
Yeah...
{{else if eq .Post.Feeling 6}}
Epic!
{{else if eq .Post.Feeling 7}}
Nyeah~♥
{{else if eq .Post.Feeling 8}}
Yes!
{{else if eq .Post.Feeling 9}}
olv.portal.miitoo.
{{else}}
Like
{{end}}
{{end}}
</span>
</button>
<div class="yeah symbol"><span class="symbol-label">Likes</span><span class="yeah-count">{{.Post.YeahCount}}</span></div>
<div class="reply symbol"><span class="symbol-label">Comments</span><span class="reply-count">{{.Post.CommentCount}}</span></div>
</div>
</div>
</div>
</section>
<div id="yeah-content"{{if not (or (.Yeahs) (.Post.Yeahed))}} class="none"{{end}}>
<a href="/users/{{.CurrentUser.Username}}" class="post-permalink-feeling-icon visitor"{{if not .Post.Yeahed}} style="display: none;"{{end}}>
{{if .CurrentUser.Role.Image}}<img src="{{.CurrentUser.Role.Image}}" class="official-tag">{{end}}
<img src="{{.CurrentUser.Avatar}}" class="user-icon">
</a>
{{range $yeah := .Yeahs}}
{{template "yeah_icon.html" $yeah}}
{{end}}
</div>
<div id="reply-content">
<h2 class="reply-label">Comments</h2>
<div class="no-reply-content{{if or .Comments .PinnedComments}} none{{end}}">
<div>
<p>This post has no comments.</p>
</div>
</div>
{{if gt .Post.CommentCount 20}}
<button data-fragment-url="/posts/{{.Post.ID}}/comments" class="more-button all-replies-button" data-reply-count="{{.Post.CommentCount}}">
<span class="symbol">Show all comments ({{.Post.CommentCount}})</span>
</button>
{{end}}
<ul class="list reply-list test-reply-list">
{{range $comment := .PinnedComments}}
{{template "render_comment.html" $comment}}
{{end}}
{{range $comment := .Comments}}
{{template "render_comment.html" $comment}}
{{end}}
</ul>
</div>
<h2 class="reply-label">Add a Comment</h2>
{{if (and (.CurrentUser.Username) (not .IsBlocked))}}
<form id="reply-form" class="for-identified-user" method="post" action="/posts/{{.Post.ID}}/comments">
<input type="hidden" name="csrfmiddlewaretoken" value="{{.CurrentUser.CSRFToken}}">
<div class="feeling-selector js-feeling-selector"><label class="symbol feeling-button feeling-button-normal checked"><input type="radio" name="feeling_id" value="0" checked><span class="symbol-label">normal</span></label><label class="symbol feeling-button feeling-button-happy"><input type="radio" name="feeling_id" value="1"><span class="symbol-label">happy</span></label><label class="symbol feeling-button feeling-button-like"><input type="radio" name="feeling_id" value="2"><span class="symbol-label">like</span></label><label class="symbol feeling-button feeling-button-surprised"><input type="radio" name="feeling_id" value="3"><span class="symbol-label">surprised</span></label><label class="symbol feeling-button feeling-button-frustrated"><input type="radio" name="feeling_id" value="4"><span class="symbol-label">frustrated</span></label><label class="symbol feeling-button feeling-button-puzzled"><input type="radio" name="feeling_id" value="5"><span class="symbol-label">puzzled</span></label></div>
<div class="textarea-with-menu active-text">
<menu class="textarea-menu">
<li><label class="textarea-menu-text"><input type="radio" name="post_type" value="0"></label></li>
<li><label class="textarea-menu-memo"><input type="radio" name="post_type" value="1"></label></li>
<span class="character-count">2000</span>
</menu>
<div class="textarea-container">
<textarea name="body" class="textarea-text textarea" maxlength="2000" placeholder="Add a comment here." data-open-folded-form data-required></textarea>
</div>
<div class="textarea-memo none">
<div id="memo-drawboard-page" class="none">
<div class="window-body">
<div class="memo-buttons">
<button type="button" class="artwork-clear"></button>
<button type="button" class="artwork-undo"></button>
<button type="button" class="artwork-pencil small selected"></button>
<button type="button" class="artwork-eraser small"></button>
<button type="button" class="artwork-fill"></button>
<input type="text" class="artwork-color">
<button type="button" class="artwork-zoom"></button>
</div>
<div class="memo-canvas">
<canvas id="artwork-canvas" zoom="2"></canvas>
<canvas id="artwork-canvas-undo"></canvas>
<canvas id="artwork-canvas-redo"></canvas>
<input type="hidden" name="painting">
</div>
<div class="form-buttons">
<input class="olv-modal-close-button black-button memo-finish-btn" type="button" value="Save">
<button type="button" class="artwork-lock none"></button>
</div>
</div>
</div>
</div>
</div>
<label class="file-button-container">
<span class="input-label">Attachment <span>Images, audio and videos are allowed.
{{if .MaxUploadSize}}Maximum upload size: {{.MaxUploadSize}}{{end}}
</span></span>
<span class="button file-upload-button">Upload</span>
<input accept="image/*, audio/*, video/*" type="file" class="file-button none">
<input type="hidden" name="image">
<input type="hidden" name="attachment_type">
<div class="screenshot-container still-image preview-container" style="display: none;">
<img class="preview-image none">
<video class="preview-video none" controls></video>
<audio class="preview-audio none" controls></audio>
</div>
<script src="/assets/js/upload.js"></script>
</label>
<div class="post-form-footer-options">
<div class="post-form-footer-option-inner post-form-spoiler js-post-form-spoiler test-post-form-spoiler">
<label class="spoiler-button symbol"><input type="checkbox" id="is_spoiler" name="is_spoiler" value="1">NSFW or spoiler</label>
</div>
</div>
<div class="form-buttons">
<input type="submit" class="black-button reply-button disabled" value="Send" data-community-id="{{.Community.ID}}" data-url-id="{{.Post.ID}}" data-post-content-type="text" data-post-with-screenshot="nodata" disabled>
</div>
</form>
{{else if .IsBlocked}}
<div class="cannot-reply">
<p>You cannot comment on this post.</p>
</div>
{{else}}
<div class="guest-message">
<p>You must sign in to post a comment.<br><br>Sign in with an SOP.epic account to connect to users around the world through games, topics and messages.</p>
<a href="/signup" class="arrow-button"><span>Sign Up</span></a>
<a href="/login" class="arrow-button"><span>Log In</span></a>
</div>
{{end}}
</div>
{{end}}
</div>
</div>
{{if .Pjax}}
{{template "footer.html"}}
{{end}}

Some files were not shown because too many files have changed in this diff Show More