Merge branch 'main' of ssh://code.freedombone.net:2222/bashrc/epicyon into main
|
@ -1,7 +1,9 @@
|
|||
@charset "UTF-8";
|
||||
|
||||
:root {
|
||||
--header-bg-color: #282c37;
|
||||
--main-bg-color: #282c37;
|
||||
--post-bg-color: #282c37;
|
||||
--column-left-color: #282c37;
|
||||
--link-bg-color: #282c37;
|
||||
--dropdown-fg-color: #dddddd;
|
||||
|
@ -46,6 +48,8 @@
|
|||
--time-vertical-align: 4px;
|
||||
--time-vertical-align-mobile: 25px;
|
||||
--publish-button-text: #FFFFFF;
|
||||
--button-margin: 5px;
|
||||
--button-left-margin: none;
|
||||
--button-text: #FFFFFF;
|
||||
--button-selected-text: #FFFFFF;
|
||||
--publish-button-background: #999;
|
||||
|
@ -62,7 +66,7 @@
|
|||
--button-height: 10px;
|
||||
--button-height-padding-mobile: 20px;
|
||||
--button-height-padding: 10px;
|
||||
--image-corners: 10%,
|
||||
--image-corners: 10%;
|
||||
--gallery-border: #ccc;
|
||||
--gallery-hover: #777;
|
||||
--gallery-text-color: #ccc;
|
||||
|
@ -70,6 +74,7 @@
|
|||
--gallery-font-size-mobile: 35px;
|
||||
--button-corner-radius: 15px;
|
||||
--timeline-border-radius: 30px;
|
||||
--timeline-posts-background-color: #282c37;
|
||||
--icons-side: right;
|
||||
--title-color: #999;
|
||||
--focus-color: white;
|
||||
|
@ -83,6 +88,8 @@
|
|||
--column-left-width: 10vw;
|
||||
--column-center-width: 80vw;
|
||||
--column-right-width: 10vw;
|
||||
--column-left-mobile-margin: 2%;
|
||||
--column-left-top-margin: 0;
|
||||
--column-left-header-style: uppercase;
|
||||
--column-left-header-background: #555;
|
||||
--column-left-header-color: #fff;
|
||||
|
@ -129,13 +136,16 @@
|
|||
--post-separator-margin-top: 0;
|
||||
--post-separator-margin-bottom: 0;
|
||||
--post-separator-width: 95%;
|
||||
--separator-width-left: 95%;
|
||||
--separator-width-right: 95%;
|
||||
--post-separator-height: 1px;
|
||||
--header-vertical-offset: 0;
|
||||
--profile-background-height: 25vw;
|
||||
--profile-text-align: left;
|
||||
--verticals-width: 0;
|
||||
--italic-font-style: italic;
|
||||
--header-font: 'Bedstead';
|
||||
--header-font: 'Arial, Helvetica, sans-serif';
|
||||
--button-bottom-margin: 10px;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
|
@ -166,13 +176,14 @@ body, html {
|
|||
|
||||
.leftColIcons {
|
||||
width: 100%;
|
||||
background-color: var(--main-bg-color);
|
||||
background-color: var(--column-left-color);
|
||||
float: right;
|
||||
display: block;
|
||||
padding-bottom: var(--column-left-icons-margin);
|
||||
}
|
||||
|
||||
.postSeparatorImage img {
|
||||
background-color: var(--post-bg-color);
|
||||
padding-top: var(--post-separator-margin-top);
|
||||
padding-bottom: var(--post-separator-margin-bottom);
|
||||
width: var(--post-separator-width);
|
||||
|
@ -180,6 +191,24 @@ body, html {
|
|||
display: block;
|
||||
}
|
||||
|
||||
.postSeparatorImageLeft img {
|
||||
background-color: var(--column-left-color);
|
||||
padding-top: var(--post-separator-margin-top);
|
||||
padding-bottom: var(--post-separator-margin-bottom);
|
||||
width: var(--separator-width-left);
|
||||
height: var(--post-separator-height);
|
||||
display: block;
|
||||
}
|
||||
|
||||
.postSeparatorImageRight img {
|
||||
background-color: var(--column-left-color);
|
||||
padding-top: var(--post-separator-margin-top);
|
||||
padding-bottom: var(--post-separator-margin-bottom);
|
||||
width: var(--separator-width-right);
|
||||
height: var(--post-separator-height);
|
||||
display: block;
|
||||
}
|
||||
|
||||
.headericons {
|
||||
display: inline-block;
|
||||
float: right;
|
||||
|
@ -217,6 +246,10 @@ blockquote p {
|
|||
margin: -4px 5px;
|
||||
}
|
||||
|
||||
.imageAnchor {
|
||||
font-family: var(--header-font);
|
||||
}
|
||||
|
||||
.imageAnchor:focus img{
|
||||
border: 2px solid var(--focus-color);
|
||||
}
|
||||
|
@ -378,6 +411,10 @@ a:focus {
|
|||
width: 10%;
|
||||
}
|
||||
|
||||
.timeline-posts {
|
||||
background-color: var(--timeline-posts-background-color);
|
||||
}
|
||||
|
||||
.container img.timelineicon:hover {
|
||||
filter: brightness(var(--icon-brightness-change));
|
||||
}
|
||||
|
@ -530,6 +567,10 @@ a:focus {
|
|||
background: var(--link-bg-color);
|
||||
}
|
||||
|
||||
.announceOrReply {
|
||||
font-family: var(--header-font);
|
||||
}
|
||||
|
||||
.container img.announceOrReply {
|
||||
float: none;
|
||||
width: 30px;
|
||||
|
@ -932,15 +973,16 @@ div.container {
|
|||
}
|
||||
.containerHeader {
|
||||
border: var(--border-width-header) solid var(--border-color);
|
||||
background-color: var(--main-bg-color);
|
||||
background-color: var(--header-bg-color);
|
||||
border-radius: var(--timeline-border-radius);
|
||||
padding: var(--header-button-padding);
|
||||
margin: var(--vertical-between-posts-header);
|
||||
transform: translateY(var(--header-vertical-offset));
|
||||
margin-bottom: var(--button-bottom-margin);
|
||||
}
|
||||
.container {
|
||||
border: var(--border-width) solid var(--border-color);
|
||||
background-color: var(--main-bg-color);
|
||||
background-color: var(--post-bg-color);
|
||||
border-radius: var(--timeline-border-radius);
|
||||
padding-left: var(--container-padding);
|
||||
padding-right: var(--container-padding);
|
||||
|
@ -954,7 +996,6 @@ div.container {
|
|||
font-family: var(--header-font);
|
||||
font-size: var(--column-left-header-size);
|
||||
text-transform: var(--column-left-header-style);
|
||||
padding: 4px;
|
||||
border: none;
|
||||
}
|
||||
.newswireItem {
|
||||
|
@ -999,13 +1040,14 @@ div.container {
|
|||
display: none;
|
||||
}
|
||||
.timeline-banner {
|
||||
vertical-align: top;
|
||||
object-fit: cover;
|
||||
width: 98vw;
|
||||
width: 100%;
|
||||
max-height: var(--banner-height);
|
||||
}
|
||||
.timeline {
|
||||
border: 0;
|
||||
width: 98vw;
|
||||
width: 100%;
|
||||
}
|
||||
.col-left a:link {
|
||||
background: var(--column-left-color);
|
||||
|
@ -1018,6 +1060,7 @@ div.container {
|
|||
width: var(--column-left-width);
|
||||
}
|
||||
.col-left {
|
||||
margin-top: var(--column-left-top-margin);
|
||||
border: var(--column-left-border-width) solid var(--column-left-border-color);
|
||||
color: var(--column-left-fg-color);
|
||||
font-size: var(--font-size-links);
|
||||
|
@ -1252,7 +1295,7 @@ div.container {
|
|||
font-family: var(--header-font);
|
||||
padding: var(--button-height-padding);
|
||||
width: 10%;
|
||||
margin: 5px;
|
||||
margin: var(--button-margin);
|
||||
min-width: var(--button-width-chars);
|
||||
transition: all 0.5s;
|
||||
cursor: pointer;
|
||||
|
@ -1260,6 +1303,8 @@ div.container {
|
|||
border-bottom: none;
|
||||
border-left: var(--tab-border-width) solid var(--tab-border-color);
|
||||
border-right: var(--tab-border-width) solid var(--tab-border-color);
|
||||
margin-bottom: var(--button-bottom-margin);
|
||||
margin-left: var(--button-left-margin);
|
||||
}
|
||||
.buttonDesktop {
|
||||
border-radius: var(--button-corner-radius);
|
||||
|
@ -1278,6 +1323,8 @@ div.container {
|
|||
border-bottom: none;
|
||||
border-left: var(--tab-border-width) solid var(--tab-border-color);
|
||||
border-right: var(--tab-border-width) solid var(--tab-border-color);
|
||||
margin-bottom: var(--button-bottom-margin);
|
||||
margin-left: var(--button-left-margin);
|
||||
}
|
||||
.publishbtn {
|
||||
border-radius: var(--button-corner-radius);
|
||||
|
@ -1322,7 +1369,7 @@ div.container {
|
|||
font-family: var(--header-font);
|
||||
padding: var(--button-height-padding);
|
||||
width: 10%;
|
||||
margin: 5px;
|
||||
margin: var(--button-margin);
|
||||
min-width: var(--button-width-chars);
|
||||
transition: all 0.5s;
|
||||
cursor: pointer;
|
||||
|
@ -1330,6 +1377,8 @@ div.container {
|
|||
border-bottom: none;
|
||||
border-left: var(--tab-border-width) solid var(--tab-border-color);
|
||||
border-right: var(--tab-border-width) solid var(--tab-border-color);
|
||||
margin-bottom: var(--button-bottom-margin);
|
||||
margin-left: var(--button-left-margin);
|
||||
}
|
||||
.buttonselectedhighlighted {
|
||||
border-radius: var(--button-corner-radius);
|
||||
|
@ -1381,6 +1430,7 @@ div.container {
|
|||
}
|
||||
.pageicon {
|
||||
width: 4%;
|
||||
background-color: var(--post-bg-color);
|
||||
}
|
||||
.time-right {
|
||||
float: var(--icons-side);
|
||||
|
@ -1564,15 +1614,16 @@ div.container {
|
|||
}
|
||||
.containerHeader {
|
||||
border: var(--border-width-header) solid var(--border-color);
|
||||
background-color: var(--main-bg-color);
|
||||
background-color: var(--header-bg-color);
|
||||
border-radius: var(--timeline-border-radius);
|
||||
padding: var(--header-button-padding);
|
||||
margin: var(--vertical-between-posts-header);
|
||||
transform: translateY(0%);
|
||||
margin-bottom: var(--button-bottom-margin);
|
||||
}
|
||||
.container {
|
||||
border: var(--border-width) solid var(--border-color);
|
||||
background-color: var(--main-bg-color);
|
||||
background-color: var(--post-bg-color);
|
||||
border-radius: var(--timeline-border-radius);
|
||||
padding-left: var(--container-padding);
|
||||
padding-right: var(--container-padding);
|
||||
|
@ -1586,7 +1637,6 @@ div.container {
|
|||
font-family: var(--header-font);
|
||||
font-size: var(--column-left-header-size-mobile);
|
||||
text-transform: var(--column-left-header-style);
|
||||
padding: 4px;
|
||||
border: none;
|
||||
}
|
||||
.leftColEditImage {
|
||||
|
@ -1657,6 +1707,7 @@ div.container {
|
|||
display: inline;
|
||||
}
|
||||
.timeline-banner {
|
||||
vertical-align: top;
|
||||
object-fit: cover;
|
||||
width: 98vw;
|
||||
max-height: var(--banner-height-mobile);
|
||||
|
@ -1671,6 +1722,9 @@ div.container {
|
|||
display: none;
|
||||
width: 0%;
|
||||
}
|
||||
.col-left-mobile {
|
||||
margin-left: var(--column-left-mobile-margin);
|
||||
}
|
||||
.col-left {
|
||||
float: left;
|
||||
width: 0%;
|
||||
|
@ -1679,6 +1733,10 @@ div.container {
|
|||
.col-center {
|
||||
width: 100vw;
|
||||
}
|
||||
.col-right-mobile {
|
||||
margin-left: var(--column-left-mobile-margin);
|
||||
margin-right: var(--column-left-mobile-margin);
|
||||
}
|
||||
.col-right {
|
||||
float: right;
|
||||
width: 0%;
|
||||
|
@ -1848,11 +1906,12 @@ div.container {
|
|||
min-width: var(--button-width-chars);
|
||||
transition: all 0.5s;
|
||||
cursor: pointer;
|
||||
margin: 5px;
|
||||
margin: var(--button-margin);
|
||||
border-top: var(--tab-border-width) solid var(--tab-border-color);
|
||||
border-bottom: none;
|
||||
border-left: var(--tab-border-width) solid var(--tab-border-color);
|
||||
border-right: var(--tab-border-width) solid var(--tab-border-color);
|
||||
margin-bottom: var(--button-bottom-margin);
|
||||
}
|
||||
.frontPageMobileButtons{
|
||||
display: block;
|
||||
|
@ -1881,11 +1940,12 @@ div.container {
|
|||
min-width: var(--button-width-chars);
|
||||
transition: all 0.5s;
|
||||
cursor: pointer;
|
||||
margin: 5px;
|
||||
margin: var(--button-margin);
|
||||
border-top: var(--tab-border-width) solid var(--tab-border-color);
|
||||
border-bottom: none;
|
||||
border-left: var(--tab-border-width) solid var(--tab-border-color);
|
||||
border-right: var(--tab-border-width) solid var(--tab-border-color);
|
||||
margin-bottom: var(--button-bottom-margin);
|
||||
}
|
||||
.buttonDesktop {
|
||||
background: transparent;
|
||||
|
@ -1910,7 +1970,7 @@ div.container {
|
|||
min-width: var(--button-width-chars);
|
||||
transition: all 0.5s;
|
||||
cursor: pointer;
|
||||
margin: 15px;
|
||||
margin: 15px 0px;
|
||||
}
|
||||
.buttonhighlighted {
|
||||
border-radius: var(--button-corner-radius);
|
||||
|
@ -1940,11 +2000,12 @@ div.container {
|
|||
min-width: var(--button-width-chars);
|
||||
transition: all 0.5s;
|
||||
cursor: pointer;
|
||||
margin: 5px;
|
||||
margin: var(--button-margin);
|
||||
border-top: var(--tab-border-width) solid var(--tab-border-color);
|
||||
border-bottom: none;
|
||||
border-left: var(--tab-border-width) solid var(--tab-border-color);
|
||||
border-right: var(--tab-border-width) solid var(--tab-border-color);
|
||||
margin-bottom: var(--button-bottom-margin);
|
||||
}
|
||||
.buttonselectedhighlighted {
|
||||
border-radius: var(--button-corner-radius);
|
||||
|
@ -1996,6 +2057,7 @@ div.container {
|
|||
}
|
||||
.pageicon {
|
||||
width: 14%;
|
||||
background-color: var(--post-bg-color);
|
||||
}
|
||||
.time-right {
|
||||
float: var(--icons-side);
|
||||
|
|
|
@ -35,6 +35,8 @@
|
|||
--focus-color: white;
|
||||
--search-banner-height: 30vh;
|
||||
--search-banner-height-mobile: 20vh;
|
||||
--title-color: #999;
|
||||
--header-font: 'Arial, Helvetica, sans-serif';
|
||||
}
|
||||
|
||||
@font-face {
|
||||
|
@ -62,6 +64,11 @@ body, html {
|
|||
min-width: 600px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: var(--header-font);
|
||||
color: var(--title-color);
|
||||
}
|
||||
|
||||
a, u {
|
||||
color: var(--main-fg-color);
|
||||
}
|
||||
|
@ -90,6 +97,13 @@ a:focus {
|
|||
border: 2px solid var(--focus-color);
|
||||
}
|
||||
|
||||
.domainHistogramLeft {
|
||||
float: right;
|
||||
}
|
||||
.domainHistogramRight {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.follow {
|
||||
background-image: url("follow-background.jpg");
|
||||
background-size: cover;
|
||||
|
@ -192,6 +206,10 @@ input[type=text] {
|
|||
}
|
||||
|
||||
@media screen and (min-width: 400px) {
|
||||
.domainHistogram {
|
||||
border: 0;
|
||||
font-size: var(--hashtag-size1);
|
||||
}
|
||||
.timeline-banner {
|
||||
object-fit: cover;
|
||||
width: 98vw;
|
||||
|
@ -244,6 +262,10 @@ input[type=text] {
|
|||
}
|
||||
|
||||
@media screen and (max-width: 1000px) {
|
||||
.domainHistogram {
|
||||
border: 0;
|
||||
font-size: var(--hashtag-size2);
|
||||
}
|
||||
.timeline-banner {
|
||||
object-fit: cover;
|
||||
width: 98vw;
|
||||
|
|
|
@ -4474,6 +4474,8 @@ class JsonLdError(Exception):
|
|||
self.causeTrace = traceback.extract_tb(*sys.exc_info()[2:])
|
||||
|
||||
def __str__(self):
|
||||
if not hasattr(self, 'message'):
|
||||
return 'Unknown exception'
|
||||
rval = repr(self.message)
|
||||
rval += '\nType: ' + self.type
|
||||
if self.code:
|
||||
|
|
|
@ -17,6 +17,9 @@
|
|||
"gallery-font-size": "35px",
|
||||
"gallery-font-size-mobile": "55px",
|
||||
"main-bg-color": "#002365",
|
||||
"post-bg-color": "#002365",
|
||||
"timeline-posts-background-color": "#002365",
|
||||
"header-bg-color": "#002365",
|
||||
"column-left-color": "#002365",
|
||||
"text-entry-background": "#002365",
|
||||
"link-bg-color": "#002365",
|
||||
|
|
|
@ -6,6 +6,9 @@
|
|||
"publish-button-at-top": "False",
|
||||
"focus-color": "green",
|
||||
"main-bg-color": "black",
|
||||
"post-bg-color": "black",
|
||||
"timeline-posts-background-color": "black",
|
||||
"header-bg-color": "black",
|
||||
"column-left-color": "black",
|
||||
"link-bg-color": "black",
|
||||
"main-bg-color-dm": "#0b0a0a",
|
||||
|
|
|
@ -14,6 +14,9 @@
|
|||
"font-size4": "24px",
|
||||
"font-size5": "22px",
|
||||
"main-bg-color": "#383335",
|
||||
"post-bg-color": "#383335",
|
||||
"timeline-posts-background-color": "#383335",
|
||||
"header-bg-color": "#383335",
|
||||
"column-left-color": "#383335",
|
||||
"text-entry-background": "#383335",
|
||||
"link-bg-color": "#383335",
|
||||
|
|
|
@ -27,6 +27,9 @@
|
|||
"font-size4": "24px",
|
||||
"font-size5": "22px",
|
||||
"main-bg-color": "black",
|
||||
"post-bg-color": "black",
|
||||
"timeline-posts-background-color": "black",
|
||||
"header-bg-color": "black",
|
||||
"column-left-header-color": "#fff",
|
||||
"column-left-header-background": "#555",
|
||||
"column-left-header-size": "20px",
|
||||
|
|
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 9.1 KiB |
|
@ -1,6 +1,10 @@
|
|||
{
|
||||
"verticals-width": "3px",
|
||||
"button-bottom-margin": "0",
|
||||
"header-vertical-offset": "10px",
|
||||
"header-bg-color": "#efefef",
|
||||
"verticals-width": "0px",
|
||||
"newswire-publish-icon": "False",
|
||||
"line-spacing-newswire": "150%",
|
||||
"full-width-timeline-buttons": "False",
|
||||
"icons-as-buttons": "True",
|
||||
"rss-icon-at-top": "False",
|
||||
|
@ -13,8 +17,8 @@
|
|||
"font-size-calendar-cell": "2rem",
|
||||
"calendar-horizontal-padding": "20%",
|
||||
"time-vertical-align": "10px",
|
||||
"header-vertical-offset": "-10%",
|
||||
"publish-button-vertical-offset": "0",
|
||||
"publish-button-vertical-offset": "15px",
|
||||
"vertical-between-posts": "0",
|
||||
"vertical-between-posts-header": "0 0",
|
||||
"header-button-padding": "0 0",
|
||||
"containericons-horizontal-spacing": "0%",
|
||||
|
@ -31,44 +35,49 @@
|
|||
"font-size-dropdown-header": "30px",
|
||||
"post-separator-margin-top": "1%",
|
||||
"post-separator-margin-bottom": "1%",
|
||||
"post-separator-width": "95%",
|
||||
"post-separator-width": "96%",
|
||||
"separator-width-left": "98%",
|
||||
"separator-width-right": "99%",
|
||||
"post-separator-height": "1px",
|
||||
"column-left-top-margin": "10px",
|
||||
"column-left-border-width": "0px",
|
||||
"column-right-border-width": "0px",
|
||||
"column-left-mobile-margin": "2%",
|
||||
"column-left-border-color": "black",
|
||||
"column-left-header-color": "black",
|
||||
"column-left-header-background": "white",
|
||||
"column-left-header-background": "#efefef",
|
||||
"column-left-header-style": "none",
|
||||
"search-banner-height": "15vh",
|
||||
"search-banner-height-mobile": "10vh",
|
||||
"container-button-padding": "0px",
|
||||
"container-button-margin": "0px",
|
||||
"column-left-icon-size": "15%",
|
||||
"column-right-icon-size": "15%",
|
||||
"column-right-icon-size": "8%",
|
||||
"button-margin": "2px",
|
||||
"button-height-padding": "5px",
|
||||
"icon-brightness-change": "70%",
|
||||
"border-width": "0px",
|
||||
"border-width-header": "0px",
|
||||
"tab-border-width": "3px",
|
||||
"tab-border-width": "0px",
|
||||
"tab-border-color": "grey",
|
||||
"button-corner-radius": "0px",
|
||||
"login-button-color": "#25408f",
|
||||
"login-button-fg-color": "white",
|
||||
"column-left-width": "10vw",
|
||||
"column-center-width": "80vw",
|
||||
"column-right-width": "10vw",
|
||||
"column-center-width": "70vw",
|
||||
"column-right-width": "20vw",
|
||||
"column-right-fg-color": "#25408f",
|
||||
"column-right-fg-color-voted-on": "red",
|
||||
"newswire-item-moderated-color": "red",
|
||||
"newswire-date-moderated-color": "red",
|
||||
"newswire-date-color": "grey",
|
||||
"timeline-border-radius": "0px",
|
||||
"button-background": "#767674",
|
||||
"button-background-hover": "#555",
|
||||
"button-text-hover": "white",
|
||||
"button-background": "#dedede",
|
||||
"button-background-hover": "white",
|
||||
"button-text-hover": "black",
|
||||
"button-selected": "white",
|
||||
"button-selected-text": "black",
|
||||
"button-text": "white",
|
||||
"button-text": "black",
|
||||
"hashtag-fg-color": "white",
|
||||
"publish-button-background": "#25408f",
|
||||
"publish-button-text": "white",
|
||||
|
@ -77,10 +86,12 @@
|
|||
"font-size-button-mobile": "26px",
|
||||
"font-size-publish-button": "14px",
|
||||
"rgba(0, 0, 0, 0.5)": "rgba(0, 0, 0, 0.0)",
|
||||
"column-left-color": "white",
|
||||
"main-bg-color": "white",
|
||||
"column-left-color": "#efefef",
|
||||
"main-bg-color": "#efefef",
|
||||
"post-bg-color": "white",
|
||||
"timeline-posts-background-color": "white",
|
||||
"main-bg-color-dm": "white",
|
||||
"link-bg-color": "white",
|
||||
"link-bg-color": "transparent",
|
||||
"main-bg-color-reply": "white",
|
||||
"main-bg-color-report": "white",
|
||||
"main-header-color-roles": "#ebebf0",
|
||||
|
|
|
@ -8,6 +8,9 @@
|
|||
"column-left-header-background": "#9fb42b",
|
||||
"column-left-header-color": "#33390d",
|
||||
"main-bg-color": "#9fb42b",
|
||||
"post-bg-color": "#9fb42b",
|
||||
"timeline-posts-background-color": "#9fb42b",
|
||||
"header-bg-color": "#9fb42b",
|
||||
"column-left-color": "#33390d",
|
||||
"column-left-fg-color": "#9fb42b",
|
||||
"link-bg-color": "#33390d",
|
||||
|
|
|
@ -22,6 +22,9 @@
|
|||
"rgba(0, 0, 0, 0.5)": "rgba(0, 0, 0, 0.0)",
|
||||
"column-left-color": "#e6ebf0",
|
||||
"main-bg-color": "#e6ebf0",
|
||||
"post-bg-color": "#e6ebf0",
|
||||
"timeline-posts-background-color": "#e6ebf0",
|
||||
"header-bg-color": "#e6ebf0",
|
||||
"main-bg-color-dm": "#e3dbf0",
|
||||
"link-bg-color": "#e6ebf0",
|
||||
"main-bg-color-reply": "#e0dbf0",
|
||||
|
|
|
@ -20,6 +20,9 @@
|
|||
"font-size4": "24px",
|
||||
"font-size5": "22px",
|
||||
"main-bg-color": "#0f0d10",
|
||||
"post-bg-color": "#0f0d10",
|
||||
"timeline-posts-background-color": "#0f0d10",
|
||||
"header-bg-color": "#0f0d10",
|
||||
"column-left-color": "#0f0d10",
|
||||
"text-entry-background": "#0f0d10",
|
||||
"link-bg-color": "#0f0d10",
|
||||
|
|
|
@ -13,6 +13,9 @@
|
|||
"font-size4": "24px",
|
||||
"font-size5": "22px",
|
||||
"main-bg-color": "#1f152d",
|
||||
"post-bg-color": "#1f152d",
|
||||
"timeline-posts-background-color": "#1f152d",
|
||||
"header-bg-color": "#1f152d",
|
||||
"column-left-color": "#1f152d",
|
||||
"link-bg-color": "#1f152d",
|
||||
"main-bg-color-reply": "#1a142d",
|
||||
|
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.5 KiB |
|
@ -41,6 +41,9 @@
|
|||
"font-size5": "12px",
|
||||
"font-size-likes": "10px",
|
||||
"main-bg-color": "#100e23",
|
||||
"post-bg-color": "#100e23",
|
||||
"timeline-posts-background-color": "#100e23",
|
||||
"header-bg-color": "#100e23",
|
||||
"column-left-color": "#0f0d10",
|
||||
"text-entry-background": "#0f0d10",
|
||||
"link-bg-color": "#0f0d10",
|
||||
|
|
|
@ -29,9 +29,12 @@
|
|||
"font-size5": "22px",
|
||||
"rgba(0, 0, 0, 0.5)": "rgba(0, 0, 0, 0.0)",
|
||||
"main-bg-color": "white",
|
||||
"post-bg-color": "white",
|
||||
"timeline-posts-background-color": "white",
|
||||
"header-bg-color": "#ddd",
|
||||
"column-left-color": "white",
|
||||
"main-bg-color-dm": "white",
|
||||
"link-bg-color": "white",
|
||||
"link-bg-color": "transparent",
|
||||
"main-bg-color-reply": "white",
|
||||
"main-bg-color-report": "white",
|
||||
"main-header-color-roles": "#ebebf0",
|
||||
|
|
|
@ -16,6 +16,9 @@
|
|||
"font-size4": "24px",
|
||||
"font-size5": "22px",
|
||||
"main-bg-color": "#0f0d10",
|
||||
"post-bg-color": "#0f0d10",
|
||||
"timeline-posts-background-color": "#0f0d10",
|
||||
"header-bg-color": "#0f0d10",
|
||||
"column-left-color": "#0f0d10",
|
||||
"text-entry-background": "#0f0d10",
|
||||
"link-bg-color": "#0f0d10",
|
||||
|
|
|
@ -8,6 +8,9 @@
|
|||
"banner-height-mobile": "10vh",
|
||||
"newswire-date-color": "yellow",
|
||||
"main-bg-color": "#5c4e41",
|
||||
"post-bg-color": "#5c4e41",
|
||||
"timeline-posts-background-color": "#5c4e41",
|
||||
"header-bg-color": "#5c4e41",
|
||||
"column-left-color": "#5c4e41",
|
||||
"text-entry-background": "#5c4e41",
|
||||
"link-bg-color": "#5c4e41",
|
||||
|
|
|
@ -329,5 +329,6 @@
|
|||
"Choose newswire items referenced in your article": "اختر العناصر الإخبارية المشار إليها في مقالتك",
|
||||
"RSS feed for your blog": "تغذية RSS لمدونتك",
|
||||
"Create a new shared item": "إنشاء عنصر مشترك جديد",
|
||||
"Rc3": "Rc3"
|
||||
"Rc3": "Rc3",
|
||||
"Hashtag origins": "أصول الهاشتاق"
|
||||
}
|
||||
|
|
|
@ -329,5 +329,6 @@
|
|||
"Choose newswire items referenced in your article": "Trieu articles de newswire als quals faci referència el vostre article",
|
||||
"RSS feed for your blog": "Feed RSS del vostre bloc",
|
||||
"Create a new shared item": "Creeu un element compartit nou",
|
||||
"Rc3": "Rc3"
|
||||
"Rc3": "Rc3",
|
||||
"Hashtag origins": "Orígens de hashtag"
|
||||
}
|
||||
|
|
|
@ -329,5 +329,6 @@
|
|||
"Choose newswire items referenced in your article": "Dewiswch eitemau newyddion y cyfeirir atynt yn eich erthygl",
|
||||
"RSS feed for your blog": "Porthiant RSS ar gyfer eich blog",
|
||||
"Create a new shared item": "Creu eitem newydd a rennir",
|
||||
"Rc3": "Rc3"
|
||||
"Rc3": "Rc3",
|
||||
"Hashtag origins": "Gwreiddiau Hashtag"
|
||||
}
|
||||
|
|
|
@ -329,5 +329,6 @@
|
|||
"Choose newswire items referenced in your article": "Wählen Sie Newswire-Artikel aus, auf die in Ihrem Artikel verwiesen wird",
|
||||
"RSS feed for your blog": "RSS-Feed für Ihr Blog",
|
||||
"Create a new shared item": "Erstellen Sie ein neues freigegebenes Element",
|
||||
"Rc3": "Rc3"
|
||||
"Rc3": "Rc3",
|
||||
"Hashtag origins": "Hashtag-Ursprünge"
|
||||
}
|
||||
|
|
|
@ -329,5 +329,6 @@
|
|||
"Choose newswire items referenced in your article": "Choose newswire items referenced in your article",
|
||||
"RSS feed for your blog": "RSS feed for your blog",
|
||||
"Create a new shared item": "Create a new shared item",
|
||||
"Rc3": "Rc3"
|
||||
"Rc3": "Rc3",
|
||||
"Hashtag origins": "Hashtag origins"
|
||||
}
|
||||
|
|
|
@ -329,5 +329,6 @@
|
|||
"Choose newswire items referenced in your article": "Elija elementos de Newswire a los que se hace referencia en su artículo",
|
||||
"RSS feed for your blog": "Fuente RSS para tu blog",
|
||||
"Create a new shared item": "Crea un nuevo elemento compartido",
|
||||
"Rc3": "Rc3"
|
||||
"Rc3": "Rc3",
|
||||
"Hashtag origins": "Orígenes del hashtag"
|
||||
}
|
||||
|
|
|
@ -329,5 +329,6 @@
|
|||
"Choose newswire items referenced in your article": "Choisissez les éléments de fil d'actualité référencés dans votre article",
|
||||
"RSS feed for your blog": "Flux RSS pour votre blog",
|
||||
"Create a new shared item": "Créer un nouvel élément partagé",
|
||||
"Rc3": "Rc3"
|
||||
"Rc3": "Rc3",
|
||||
"Hashtag origins": "Origines des hashtags"
|
||||
}
|
||||
|
|
|
@ -329,5 +329,6 @@
|
|||
"Choose newswire items referenced in your article": "Roghnaigh míreanna sreanga nuachta dá dtagraítear i d’alt",
|
||||
"RSS feed for your blog": "Fotha RSS do do bhlag",
|
||||
"Create a new shared item": "Cruthaigh mír nua roinnte",
|
||||
"Rc3": "Rc3"
|
||||
"Rc3": "Rc3",
|
||||
"Hashtag origins": "Bunús Hashtag"
|
||||
}
|
||||
|
|
|
@ -329,5 +329,6 @@
|
|||
"Choose newswire items referenced in your article": "अपने लेख में संदर्भित newswire आइटम चुनें",
|
||||
"RSS feed for your blog": "RSS आपके ब्लॉग के लिए फ़ीड करता है",
|
||||
"Create a new shared item": "एक नया साझा आइटम बनाएं",
|
||||
"Rc3": "Rc3"
|
||||
"Rc3": "Rc3",
|
||||
"Hashtag origins": "हैशटैग की उत्पत्ति"
|
||||
}
|
||||
|
|
|
@ -329,5 +329,6 @@
|
|||
"Choose newswire items referenced in your article": "Scegli gli articoli del newswire a cui fa riferimento il tuo articolo",
|
||||
"RSS feed for your blog": "Feed RSS per il tuo blog",
|
||||
"Create a new shared item": "Crea un nuovo elemento condiviso",
|
||||
"Rc3": "Rc3"
|
||||
"Rc3": "Rc3",
|
||||
"Hashtag origins": "Origini hashtag"
|
||||
}
|
||||
|
|
|
@ -329,5 +329,6 @@
|
|||
"Choose newswire items referenced in your article": "あなたの記事で参照されているニュースワイヤーアイテムを選択してください",
|
||||
"RSS feed for your blog": "ブログのRSSフィード",
|
||||
"Create a new shared item": "新しい共有アイテムを作成する",
|
||||
"Rc3": "Rc3"
|
||||
"Rc3": "Rc3",
|
||||
"Hashtag origins": "ハッシュタグの起源"
|
||||
}
|
||||
|
|
|
@ -325,5 +325,6 @@
|
|||
"Choose newswire items referenced in your article": "Choose newswire items referenced in your article",
|
||||
"RSS feed for your blog": "RSS feed for your blog",
|
||||
"Create a new shared item": "Create a new shared item",
|
||||
"Rc3": "Rc3"
|
||||
"Rc3": "Rc3",
|
||||
"Hashtag origins": "Hashtag origins"
|
||||
}
|
||||
|
|
|
@ -329,5 +329,6 @@
|
|||
"Choose newswire items referenced in your article": "Escolha os itens de notícias mencionados em seu artigo",
|
||||
"RSS feed for your blog": "Feed RSS para o seu blog",
|
||||
"Create a new shared item": "Crie um novo item compartilhado",
|
||||
"Rc3": "Rc3"
|
||||
"Rc3": "Rc3",
|
||||
"Hashtag origins": "Origens de hashtag"
|
||||
}
|
||||
|
|
|
@ -329,5 +329,6 @@
|
|||
"Choose newswire items referenced in your article": "Выберите элементы ленты новостей, на которые есть ссылки в вашей статье",
|
||||
"RSS feed for your blog": "RSS-канал для вашего блога",
|
||||
"Create a new shared item": "Создать новый общий элемент",
|
||||
"Rc3": "Rc3"
|
||||
"Rc3": "Rc3",
|
||||
"Hashtag origins": "Происхождение хэштегов"
|
||||
}
|
||||
|
|
|
@ -329,5 +329,6 @@
|
|||
"Choose newswire items referenced in your article": "选择文章中引用的新闻专栏文章",
|
||||
"RSS feed for your blog": "您博客的RSS供稿",
|
||||
"Create a new shared item": "创建一个新的共享项目",
|
||||
"Rc3": "Rc3"
|
||||
"Rc3": "Rc3",
|
||||
"Hashtag origins": "标签起源"
|
||||
}
|
||||
|
|
|
@ -227,6 +227,7 @@ def htmlLinksMobile(cssCache: {}, baseDir: str,
|
|||
'<img loading="lazy" class="timeline-banner" ' + \
|
||||
'src="/users/' + nickname + '/' + bannerFile + '" /></a>\n'
|
||||
|
||||
htmlStr += '<div class="col-left-mobile">\n'
|
||||
htmlStr += '<center>' + \
|
||||
headerButtonsFrontScreen(translate, nickname,
|
||||
'links', authorized,
|
||||
|
@ -237,6 +238,10 @@ def htmlLinksMobile(cssCache: {}, baseDir: str,
|
|||
iconsPath, editor,
|
||||
False, timelinePath,
|
||||
rssIconAtTop, False, False)
|
||||
|
||||
# end of col-left-mobile
|
||||
htmlStr += '</div>\n'
|
||||
|
||||
htmlStr += '</div>\n' + htmlFooter()
|
||||
return htmlStr
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import os
|
|||
from datetime import datetime
|
||||
from shutil import copyfile
|
||||
from content import removeLongWords
|
||||
from utils import removeHtml
|
||||
from utils import locatePost
|
||||
from utils import loadJson
|
||||
from utils import getConfigParam
|
||||
|
@ -205,6 +206,14 @@ def htmlNewswire(baseDir: str, newswire: {}, nickname: str, moderator: bool,
|
|||
separatorStr = htmlPostSeparator(baseDir, 'right')
|
||||
htmlStr = ''
|
||||
for dateStr, item in newswire.items():
|
||||
item[0] = removeHtml(item[0]).strip()
|
||||
if not item[0]:
|
||||
continue
|
||||
# remove any CDATA
|
||||
if 'CDATA[' in item[0]:
|
||||
item[0] = item[0].split('CDATA[')[1]
|
||||
if ']' in item[0]:
|
||||
item[0] = item[0].split(']')[0]
|
||||
publishedDate = \
|
||||
datetime.strptime(dateStr, "%Y-%m-%d %H:%M:%S%z")
|
||||
dateShown = publishedDate.strftime("%Y-%m-%d %H:%M")
|
||||
|
@ -346,6 +355,14 @@ def htmlCitations(baseDir: str, nickname: str, domain: str,
|
|||
if newswire:
|
||||
ctr = 0
|
||||
for dateStr, item in newswire.items():
|
||||
item[0] = removeHtml(item[0]).strip()
|
||||
if not item[0]:
|
||||
continue
|
||||
# remove any CDATA
|
||||
if 'CDATA[' in item[0]:
|
||||
item[0] = item[0].split('CDATA[')[1]
|
||||
if ']' in item[0]:
|
||||
item[0] = item[0].split(']')[0]
|
||||
# should this checkbox be selected?
|
||||
selectedStr = ''
|
||||
if dateStr in citationsSelected:
|
||||
|
@ -416,6 +433,8 @@ def htmlNewswireMobile(cssCache: {}, baseDir: str, nickname: str,
|
|||
'<img loading="lazy" class="timeline-banner" ' + \
|
||||
'src="/users/' + nickname + '/' + bannerFile + '" /></a>\n'
|
||||
|
||||
htmlStr += '<div class="col-right-mobile">\n'
|
||||
|
||||
htmlStr += '<center>' + \
|
||||
headerButtonsFrontScreen(translate, nickname,
|
||||
'newswire', authorized,
|
||||
|
@ -428,6 +447,9 @@ def htmlNewswireMobile(cssCache: {}, baseDir: str, nickname: str,
|
|||
False, timelinePath, showPublishButton,
|
||||
showPublishAsIcon, rssIconAtTop, False,
|
||||
authorized, False)
|
||||
# end of col-right-mobile
|
||||
htmlStr += '</div\n>'
|
||||
|
||||
htmlStr += htmlFooter()
|
||||
return htmlStr
|
||||
|
||||
|
|
|
@ -0,0 +1,143 @@
|
|||
__filename__ = "webapp_hashtagswarm.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
|
||||
import os
|
||||
from blocking import isBlockedHashtag
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
def getHashtagDomainMax(domainHistogram: {}) -> str:
|
||||
"""Returns the domain with the maximum number of hashtags
|
||||
"""
|
||||
maxCount = 1
|
||||
maxDomain = None
|
||||
for domain, count in domainHistogram.items():
|
||||
if count > maxCount:
|
||||
maxDomain = domain
|
||||
maxCount = count
|
||||
return maxDomain
|
||||
|
||||
|
||||
def getHashtagDomainHistogram(domainHistogram: {}, translate: {}) -> str:
|
||||
"""Returns the html for a histogram of domains
|
||||
from which hashtags are coming
|
||||
"""
|
||||
totalCount = 0
|
||||
for domain, count in domainHistogram.items():
|
||||
totalCount += count
|
||||
if totalCount == 0:
|
||||
return ''
|
||||
|
||||
htmlStr = ''
|
||||
histogramHeaderStr = '<br><br><center>\n'
|
||||
histogramHeaderStr += ' <h1>' + translate['Hashtag origins'] + '</h1>\n'
|
||||
histogramHeaderStr += ' <table class="domainHistogram">\n'
|
||||
histogramHeaderStr += ' <colgroup>\n'
|
||||
histogramHeaderStr += ' <col span="1" class="domainHistogramLeft">\n'
|
||||
histogramHeaderStr += ' <col span="1" class="domainHistogramRight">\n'
|
||||
histogramHeaderStr += ' </colgroup>\n'
|
||||
histogramHeaderStr += ' <tbody>\n'
|
||||
histogramHeaderStr += ' <tr>\n'
|
||||
|
||||
leftColStr = ''
|
||||
rightColStr = ''
|
||||
|
||||
for i in range(len(domainHistogram)):
|
||||
domain = getHashtagDomainMax(domainHistogram)
|
||||
if not domain:
|
||||
break
|
||||
percent = int(domainHistogram[domain] * 100 / totalCount)
|
||||
if histogramHeaderStr:
|
||||
htmlStr += histogramHeaderStr
|
||||
histogramHeaderStr = None
|
||||
leftColStr += str(percent) + '%<br>'
|
||||
rightColStr += domain + '<br>'
|
||||
del domainHistogram[domain]
|
||||
|
||||
if htmlStr:
|
||||
htmlStr += ' <td>' + leftColStr + '</td>\n'
|
||||
htmlStr += ' <td>' + rightColStr + '</td>\n'
|
||||
htmlStr += ' </tr>\n'
|
||||
htmlStr += ' </tbody>\n'
|
||||
htmlStr += ' </table>\n'
|
||||
htmlStr += '</center>\n'
|
||||
|
||||
return htmlStr
|
||||
|
||||
|
||||
def htmlHashTagSwarm(baseDir: str, actor: str, translate: {}) -> str:
|
||||
"""Returns a tag swarm of today's hashtags
|
||||
"""
|
||||
currTime = datetime.utcnow()
|
||||
daysSinceEpoch = (currTime - datetime(1970, 1, 1)).days
|
||||
daysSinceEpochStr = str(daysSinceEpoch) + ' '
|
||||
tagSwarm = []
|
||||
domainHistogram = {}
|
||||
|
||||
for subdir, dirs, files in os.walk(baseDir + '/tags'):
|
||||
for f in files:
|
||||
tagsFilename = os.path.join(baseDir + '/tags', f)
|
||||
if not os.path.isfile(tagsFilename):
|
||||
continue
|
||||
# get last modified datetime
|
||||
modTimesinceEpoc = os.path.getmtime(tagsFilename)
|
||||
lastModifiedDate = datetime.fromtimestamp(modTimesinceEpoc)
|
||||
fileDaysSinceEpoch = (lastModifiedDate - datetime(1970, 1, 1)).days
|
||||
# check if the file was last modified today
|
||||
if fileDaysSinceEpoch != daysSinceEpoch:
|
||||
continue
|
||||
|
||||
hashTagName = f.split('.')[0]
|
||||
if isBlockedHashtag(baseDir, hashTagName):
|
||||
continue
|
||||
if daysSinceEpochStr not in open(tagsFilename).read():
|
||||
continue
|
||||
with open(tagsFilename, 'r') as tagsFile:
|
||||
while True:
|
||||
line = tagsFile.readline()
|
||||
if not line:
|
||||
break
|
||||
elif ' ' not in line:
|
||||
break
|
||||
sections = line.split(' ')
|
||||
if len(sections) != 3:
|
||||
break
|
||||
postDaysSinceEpochStr = sections[0]
|
||||
if not postDaysSinceEpochStr.isdigit():
|
||||
break
|
||||
postDaysSinceEpoch = int(postDaysSinceEpochStr)
|
||||
if postDaysSinceEpoch < daysSinceEpoch - 1:
|
||||
break
|
||||
else:
|
||||
postUrl = sections[2]
|
||||
if '##' not in postUrl:
|
||||
break
|
||||
postDomain = postUrl.split('##')[1]
|
||||
if '#' in postDomain:
|
||||
postDomain = postDomain.split('#')[0]
|
||||
if domainHistogram.get(postDomain):
|
||||
domainHistogram[postDomain] = \
|
||||
domainHistogram[postDomain] + 1
|
||||
else:
|
||||
domainHistogram[postDomain] = 1
|
||||
tagSwarm.append(hashTagName)
|
||||
break
|
||||
|
||||
if not tagSwarm:
|
||||
return ''
|
||||
tagSwarm.sort()
|
||||
tagSwarmStr = ''
|
||||
ctr = 0
|
||||
for tagName in tagSwarm:
|
||||
tagSwarmStr += \
|
||||
'<a href="' + actor + '/tags/' + tagName + \
|
||||
'" class="hashtagswarm">' + tagName + '</a>\n'
|
||||
ctr += 1
|
||||
tagSwarmHtml = tagSwarmStr.strip() + '\n'
|
||||
tagSwarmHtml += getHashtagDomainHistogram(domainHistogram, translate)
|
||||
return tagSwarmHtml
|
|
@ -0,0 +1,330 @@
|
|||
__filename__ = "webapp_headerbuttons.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
|
||||
|
||||
import time
|
||||
from datetime import datetime
|
||||
from happening import todaysEventsCheck
|
||||
from happening import thisWeeksEventsCheck
|
||||
from webapp_utils import htmlHighlightLabel
|
||||
|
||||
|
||||
def headerButtonsTimeline(defaultTimeline: str,
|
||||
boxName: str,
|
||||
pageNumber: int,
|
||||
translate: {},
|
||||
usersPath: str,
|
||||
mediaButton: str,
|
||||
blogsButton: str,
|
||||
newsButton: str,
|
||||
inboxButton: str,
|
||||
dmButton: str,
|
||||
newDM: str,
|
||||
repliesButton: str,
|
||||
newReply: str,
|
||||
minimal: bool,
|
||||
sentButton: str,
|
||||
sharesButtonStr: str,
|
||||
bookmarksButtonStr: str,
|
||||
eventsButtonStr: str,
|
||||
moderationButtonStr: str,
|
||||
newPostButtonStr: str,
|
||||
baseDir: str,
|
||||
nickname: str, domain: str,
|
||||
iconsPath: str,
|
||||
timelineStartTime,
|
||||
newCalendarEvent: bool,
|
||||
calendarPath: str,
|
||||
calendarImage: str,
|
||||
followApprovals: str,
|
||||
iconsAsButtons: bool) -> str:
|
||||
"""Returns the header at the top of the timeline, containing
|
||||
buttons for inbox, outbox, search, calendar, etc
|
||||
"""
|
||||
# start of the button header with inbox, outbox, etc
|
||||
tlStr = '<div class="containerHeader">\n'
|
||||
# first button
|
||||
if defaultTimeline == 'tlmedia':
|
||||
tlStr += \
|
||||
'<a href="' + usersPath + \
|
||||
'/tlmedia"><button class="' + \
|
||||
mediaButton + '"><span>' + translate['Media'] + \
|
||||
'</span></button></a>'
|
||||
elif defaultTimeline == 'tlblogs':
|
||||
tlStr += \
|
||||
'<a href="' + usersPath + \
|
||||
'/tlblogs"><button class="' + \
|
||||
blogsButton + '"><span>' + translate['Blogs'] + \
|
||||
'</span></button></a>'
|
||||
elif defaultTimeline == 'tlnews':
|
||||
tlStr += \
|
||||
'<a href="' + usersPath + \
|
||||
'/tlnews"><button class="' + \
|
||||
newsButton + '"><span>' + translate['Features'] + \
|
||||
'</span></button></a>'
|
||||
else:
|
||||
tlStr += \
|
||||
'<a href="' + usersPath + \
|
||||
'/inbox"><button class="' + \
|
||||
inboxButton + '"><span>' + \
|
||||
translate['Inbox'] + '</span></button></a>'
|
||||
|
||||
# if this is a news instance and we are viewing the news timeline
|
||||
newsHeader = False
|
||||
if defaultTimeline == 'tlnews' and boxName == 'tlnews':
|
||||
newsHeader = True
|
||||
|
||||
if not newsHeader:
|
||||
tlStr += \
|
||||
'<a href="' + usersPath + \
|
||||
'/dm"><button class="' + dmButton + \
|
||||
'"><span>' + htmlHighlightLabel(translate['DM'], newDM) + \
|
||||
'</span></button></a>'
|
||||
|
||||
tlStr += \
|
||||
'<a href="' + usersPath + '/tlreplies"><button class="' + \
|
||||
repliesButton + '"><span>' + \
|
||||
htmlHighlightLabel(translate['Replies'], newReply) + \
|
||||
'</span></button></a>'
|
||||
|
||||
# typically the media button
|
||||
if defaultTimeline != 'tlmedia':
|
||||
if not minimal and not newsHeader:
|
||||
tlStr += \
|
||||
'<a href="' + usersPath + \
|
||||
'/tlmedia"><button class="' + \
|
||||
mediaButton + '"><span>' + translate['Media'] + \
|
||||
'</span></button></a>'
|
||||
else:
|
||||
if not minimal:
|
||||
tlStr += \
|
||||
'<a href="' + usersPath + \
|
||||
'/inbox"><button class="' + \
|
||||
inboxButton+'"><span>' + translate['Inbox'] + \
|
||||
'</span></button></a>'
|
||||
|
||||
isFeaturesTimeline = \
|
||||
defaultTimeline == 'tlnews' and boxName == 'tlnews'
|
||||
|
||||
if not isFeaturesTimeline:
|
||||
# typically the blogs button
|
||||
# but may change if this is a blogging oriented instance
|
||||
if defaultTimeline != 'tlblogs':
|
||||
if not minimal and not isFeaturesTimeline:
|
||||
titleStr = translate['Blogs']
|
||||
if defaultTimeline == 'tlnews':
|
||||
titleStr = translate['Article']
|
||||
tlStr += \
|
||||
'<a href="' + usersPath + \
|
||||
'/tlblogs"><button class="' + \
|
||||
blogsButton + '"><span>' + titleStr + \
|
||||
'</span></button></a>'
|
||||
else:
|
||||
if not minimal:
|
||||
tlStr += \
|
||||
'<a href="' + usersPath + \
|
||||
'/inbox"><button class="' + \
|
||||
inboxButton + '"><span>' + translate['Inbox'] + \
|
||||
'</span></button></a>'
|
||||
|
||||
# typically the news button
|
||||
# but may change if this is a news oriented instance
|
||||
if defaultTimeline != 'tlnews':
|
||||
tlStr += \
|
||||
'<a href="' + usersPath + \
|
||||
'/tlnews"><button class="' + \
|
||||
newsButton + '"><span>' + translate['News'] + \
|
||||
'</span></button></a>'
|
||||
else:
|
||||
if not newsHeader:
|
||||
tlStr += \
|
||||
'<a href="' + usersPath + \
|
||||
'/inbox"><button class="' + \
|
||||
inboxButton + '"><span>' + translate['Inbox'] + \
|
||||
'</span></button></a>'
|
||||
|
||||
# show todays events buttons on the first inbox page
|
||||
happeningStr = ''
|
||||
if boxName == 'inbox' and pageNumber == 1:
|
||||
if todaysEventsCheck(baseDir, nickname, domain):
|
||||
now = datetime.now()
|
||||
|
||||
# happening today button
|
||||
if not iconsAsButtons:
|
||||
happeningStr += \
|
||||
'<a href="' + usersPath + '/calendar?year=' + \
|
||||
str(now.year) + '?month=' + str(now.month) + \
|
||||
'?day=' + str(now.day) + '">' + \
|
||||
'<button class="buttonevent">' + \
|
||||
translate['Happening Today'] + '</button></a>'
|
||||
else:
|
||||
happeningStr += \
|
||||
'<a href="' + usersPath + '/calendar?year=' + \
|
||||
str(now.year) + '?month=' + str(now.month) + \
|
||||
'?day=' + str(now.day) + '">' + \
|
||||
'<button class="button">' + \
|
||||
translate['Happening Today'] + '</button></a>'
|
||||
|
||||
# happening this week button
|
||||
if thisWeeksEventsCheck(baseDir, nickname, domain):
|
||||
if not iconsAsButtons:
|
||||
happeningStr += \
|
||||
'<a href="' + usersPath + \
|
||||
'/calendar"><button class="buttonevent">' + \
|
||||
translate['Happening This Week'] + '</button></a>'
|
||||
else:
|
||||
happeningStr += \
|
||||
'<a href="' + usersPath + \
|
||||
'/calendar"><button class="button">' + \
|
||||
translate['Happening This Week'] + '</button></a>'
|
||||
else:
|
||||
# happening this week button
|
||||
if thisWeeksEventsCheck(baseDir, nickname, domain):
|
||||
if not iconsAsButtons:
|
||||
happeningStr += \
|
||||
'<a href="' + usersPath + \
|
||||
'/calendar"><button class="buttonevent">' + \
|
||||
translate['Happening This Week'] + '</button></a>'
|
||||
else:
|
||||
happeningStr += \
|
||||
'<a href="' + usersPath + \
|
||||
'/calendar"><button class="button">' + \
|
||||
translate['Happening This Week'] + '</button></a>'
|
||||
|
||||
if not newsHeader:
|
||||
# button for the outbox
|
||||
tlStr += \
|
||||
'<a href="' + usersPath + \
|
||||
'/outbox"><button class="' + \
|
||||
sentButton + '"><span>' + translate['Outbox'] + \
|
||||
'</span></button></a>'
|
||||
|
||||
# add other buttons
|
||||
tlStr += \
|
||||
sharesButtonStr + bookmarksButtonStr + eventsButtonStr + \
|
||||
moderationButtonStr + happeningStr + newPostButtonStr
|
||||
|
||||
if not newsHeader:
|
||||
if not iconsAsButtons:
|
||||
# the search icon
|
||||
tlStr += \
|
||||
'<a class="imageAnchor" href="' + usersPath + \
|
||||
'/search"><img loading="lazy" src="/' + \
|
||||
iconsPath + '/search.png" title="' + \
|
||||
translate['Search and follow'] + '" alt="| ' + \
|
||||
translate['Search and follow'] + \
|
||||
'" class="timelineicon"/></a>'
|
||||
else:
|
||||
# the search button
|
||||
tlStr += \
|
||||
'<a href="' + usersPath + \
|
||||
'/search"><button class="button">' + \
|
||||
'<span>' + translate['Search'] + \
|
||||
'</span></button></a>'
|
||||
|
||||
# benchmark 5
|
||||
timeDiff = int((time.time() - timelineStartTime) * 1000)
|
||||
if timeDiff > 100:
|
||||
print('TIMELINE TIMING ' + boxName + ' 5 = ' + str(timeDiff))
|
||||
|
||||
# the calendar button
|
||||
if not isFeaturesTimeline:
|
||||
calendarAltText = translate['Calendar']
|
||||
if newCalendarEvent:
|
||||
# indicate that the calendar icon is highlighted
|
||||
calendarAltText = '*' + calendarAltText + '*'
|
||||
if not iconsAsButtons:
|
||||
tlStr += \
|
||||
' <a class="imageAnchor" href="' + \
|
||||
usersPath + calendarPath + \
|
||||
'"><img loading="lazy" src="/' + iconsPath + '/' + \
|
||||
calendarImage + '" title="' + translate['Calendar'] + \
|
||||
'" alt="| ' + calendarAltText + \
|
||||
'" class="timelineicon"/></a>\n'
|
||||
else:
|
||||
tlStr += \
|
||||
'<a href="' + usersPath + calendarPath + \
|
||||
'"><button class="button">' + \
|
||||
'<span>' + translate['Calendar'] + \
|
||||
'</span></button></a>'
|
||||
|
||||
if not newsHeader:
|
||||
# the show/hide button, for a simpler header appearance
|
||||
if not iconsAsButtons:
|
||||
tlStr += \
|
||||
' <a class="imageAnchor" href="' + \
|
||||
usersPath + '/minimal' + \
|
||||
'"><img loading="lazy" src="/' + iconsPath + \
|
||||
'/showhide.png" title="' + translate['Show/Hide Buttons'] + \
|
||||
'" alt="| ' + translate['Show/Hide Buttons'] + \
|
||||
'" class="timelineicon"/></a>\n'
|
||||
else:
|
||||
tlStr += \
|
||||
'<a href="' + usersPath + '/minimal' + \
|
||||
'"><button class="button">' + \
|
||||
'<span>' + translate['Show/Hide Buttons'] + \
|
||||
'</span></button></a>'
|
||||
|
||||
if newsHeader:
|
||||
tlStr += \
|
||||
'<a href="' + usersPath + '/inbox">' + \
|
||||
'<button class="button">' + \
|
||||
'<span>' + translate['User'] + '</span></button></a>'
|
||||
|
||||
# the newswire button to show right column links
|
||||
if not iconsAsButtons:
|
||||
tlStr += \
|
||||
'<a class="imageAnchorMobile" href="' + \
|
||||
usersPath + '/newswiremobile">' + \
|
||||
'<img loading="lazy" src="/' + iconsPath + \
|
||||
'/newswire.png" title="' + translate['News'] + \
|
||||
'" alt="| ' + translate['News'] + \
|
||||
'" class="timelineicon"/></a>'
|
||||
else:
|
||||
# NOTE: deliberately no \n at end of line
|
||||
tlStr += \
|
||||
'<a href="' + \
|
||||
usersPath + '/newswiremobile' + \
|
||||
'"><button class="buttonMobile">' + \
|
||||
'<span>' + translate['Newswire'] + \
|
||||
'</span></button></a>'
|
||||
|
||||
# the links button to show left column links
|
||||
if not iconsAsButtons:
|
||||
tlStr += \
|
||||
'<a class="imageAnchorMobile" href="' + \
|
||||
usersPath + '/linksmobile">' + \
|
||||
'<img loading="lazy" src="/' + iconsPath + \
|
||||
'/links.png" title="' + translate['Edit Links'] + \
|
||||
'" alt="| ' + translate['Edit Links'] + \
|
||||
'" class="timelineicon"/></a>'
|
||||
else:
|
||||
# NOTE: deliberately no \n at end of line
|
||||
tlStr += \
|
||||
'<a href="' + \
|
||||
usersPath + '/linksmobile' + \
|
||||
'"><button class="buttonMobile">' + \
|
||||
'<span>' + translate['Links'] + \
|
||||
'</span></button></a>'
|
||||
|
||||
if newsHeader:
|
||||
tlStr += \
|
||||
'<a href="' + usersPath + '/editprofile">' + \
|
||||
'<button class="buttonDesktop">' + \
|
||||
'<span>' + translate['Settings'] + '</span></button></a>'
|
||||
|
||||
if not newsHeader:
|
||||
tlStr += followApprovals
|
||||
|
||||
if not iconsAsButtons:
|
||||
# end of headericons div
|
||||
tlStr += '</div>'
|
||||
|
||||
# end of the button header with inbox, outbox, etc
|
||||
tlStr += ' </div>\n'
|
||||
return tlStr
|
|
@ -758,7 +758,8 @@ def individualPostAsHtml(allowDownloads: bool,
|
|||
iconsPath + '/repeat_inactive.png" ' + \
|
||||
'class="announceOrReply"/>\n' + \
|
||||
' <a href="' + \
|
||||
postJsonObject['object']['id'] + '">' + \
|
||||
postJsonObject['object']['id'] + '" ' + \
|
||||
'class="announceOrReply">' + \
|
||||
announceDisplayName + '</a>\n'
|
||||
# show avatar of person replied to
|
||||
announceActor = \
|
||||
|
@ -804,7 +805,8 @@ def individualPostAsHtml(allowDownloads: bool,
|
|||
'/repeat_inactive.png" ' + \
|
||||
'class="announceOrReply"/>\n' + \
|
||||
' <a href="' + \
|
||||
postJsonObject['object']['id'] + '">@' + \
|
||||
postJsonObject['object']['id'] + '" ' + \
|
||||
'class="announceOrReply">@' + \
|
||||
announceNickname + '@' + \
|
||||
announceDomain + '</a>\n'
|
||||
else:
|
||||
|
@ -816,7 +818,7 @@ def individualPostAsHtml(allowDownloads: bool,
|
|||
'class="announceOrReply"/>\n' + \
|
||||
' <a href="' + \
|
||||
postJsonObject['object']['id'] + \
|
||||
'">@unattributed</a>\n'
|
||||
'" class="announceOrReply">@unattributed</a>\n'
|
||||
else:
|
||||
titleStr += \
|
||||
' ' + \
|
||||
|
@ -826,7 +828,8 @@ def individualPostAsHtml(allowDownloads: bool,
|
|||
'/repeat_inactive.png" ' + \
|
||||
'class="announceOrReply"/>\n' + \
|
||||
' <a href="' + \
|
||||
postJsonObject['object']['id'] + '">@unattributed</a>\n'
|
||||
postJsonObject['object']['id'] + '" ' + \
|
||||
'class="announceOrReply">@unattributed</a>\n'
|
||||
else:
|
||||
if postJsonObject['object'].get('inReplyTo'):
|
||||
containerClassIcons = 'containericons darker'
|
||||
|
@ -892,7 +895,8 @@ def individualPostAsHtml(allowDownloads: bool,
|
|||
'class="announceOrReply"/>\n' + \
|
||||
' ' + \
|
||||
'<a href="' + inReplyTo + \
|
||||
'">' + replyDisplayName + '</a>\n'
|
||||
'" class="announceOrReply">' + \
|
||||
replyDisplayName + '</a>\n'
|
||||
|
||||
# benchmark 13.7
|
||||
if not allowDownloads:
|
||||
|
@ -953,7 +957,8 @@ def individualPostAsHtml(allowDownloads: bool,
|
|||
iconsPath + '/reply.png" ' + \
|
||||
'class="announceOrReply"/>\n' + \
|
||||
' <a href="' + \
|
||||
inReplyTo + '">@' + \
|
||||
inReplyTo + '" ' + \
|
||||
'class="announceOrReply">@' + \
|
||||
replyNickname + '@' + \
|
||||
replyDomain + '</a>\n'
|
||||
else:
|
||||
|
@ -967,7 +972,7 @@ def individualPostAsHtml(allowDownloads: bool,
|
|||
'/reply.png" class="announceOrReply"/>\n' + \
|
||||
' <a href="' + \
|
||||
postJsonObject['object']['inReplyTo'] + \
|
||||
'">@unknown</a>\n'
|
||||
'" class="announceOrReply">@unknown</a>\n'
|
||||
else:
|
||||
postDomain = \
|
||||
postJsonObject['object']['inReplyTo']
|
||||
|
@ -986,7 +991,8 @@ def individualPostAsHtml(allowDownloads: bool,
|
|||
'class="announceOrReply"/>\n' + \
|
||||
' <a href="' + \
|
||||
postJsonObject['object']['inReplyTo'] + \
|
||||
'">' + postDomain + '</a>\n'
|
||||
'" class="announceOrReply">' + \
|
||||
postDomain + '</a>\n'
|
||||
|
||||
# benchmark 14
|
||||
if not allowDownloads:
|
||||
|
|
|
@ -614,9 +614,6 @@ def htmlProfile(rssIconAtTop: bool,
|
|||
if isSystemAccount(nickname):
|
||||
bannerFile, bannerFilename = \
|
||||
getBannerFile(baseDir, nickname, domain)
|
||||
# profileStyle = \
|
||||
# profileStyle.replace('banner.png',
|
||||
# '/users/' + nickname + '/' + bannerFile)
|
||||
|
||||
licenseStr = \
|
||||
'<a href="https://gitlab.com/bashrc2/epicyon">' + \
|
||||
|
@ -733,7 +730,7 @@ def htmlProfilePosts(recentPostsCache: {}, maxRecentPosts: int,
|
|||
showPublishedDateOnly,
|
||||
False, False, False, True, False)
|
||||
if postStr:
|
||||
profileStr += separatorStr + postStr
|
||||
profileStr += postStr + separatorStr
|
||||
ctr += 1
|
||||
if ctr >= maxItems:
|
||||
break
|
||||
|
|
493
webapp_search.py
|
@ -28,7 +28,7 @@ from webapp_utils import htmlFooter
|
|||
from webapp_utils import getSearchBannerFile
|
||||
from webapp_utils import htmlPostSeparator
|
||||
from webapp_post import individualPostAsHtml
|
||||
from blocking import isBlockedHashtag
|
||||
from webapp_hashtagswarm import htmlHashTagSwarm
|
||||
|
||||
|
||||
def htmlSearchEmoji(cssCache: {}, translate: {},
|
||||
|
@ -372,7 +372,7 @@ def htmlSearch(cssCache: {}, translate: {},
|
|||
'name="submitSearch">' + translate['Submit'] + '</button>\n'
|
||||
followStr += ' </form>\n'
|
||||
followStr += ' <p class="hashtagswarm">' + \
|
||||
htmlHashTagSwarm(baseDir, actor) + '</p>\n'
|
||||
htmlHashTagSwarm(baseDir, actor, translate) + '</p>\n'
|
||||
followStr += ' </center>\n'
|
||||
followStr += ' </div>\n'
|
||||
followStr += '</div>\n'
|
||||
|
@ -380,79 +380,221 @@ def htmlSearch(cssCache: {}, translate: {},
|
|||
return followStr
|
||||
|
||||
|
||||
def htmlHashTagSwarm(baseDir: str, actor: str) -> str:
|
||||
"""Returns a tag swarm of today's hashtags
|
||||
def htmlSkillsSearch(cssCache: {}, translate: {}, baseDir: str,
|
||||
httpPrefix: str,
|
||||
skillsearch: str, instanceOnly: bool,
|
||||
postsPerPage: int) -> str:
|
||||
"""Show a page containing search results for a skill
|
||||
"""
|
||||
currTime = datetime.utcnow()
|
||||
daysSinceEpoch = (currTime - datetime(1970, 1, 1)).days
|
||||
daysSinceEpochStr = str(daysSinceEpoch) + ' '
|
||||
tagSwarm = []
|
||||
if skillsearch.startswith('*'):
|
||||
skillsearch = skillsearch[1:].strip()
|
||||
|
||||
for subdir, dirs, files in os.walk(baseDir + '/tags'):
|
||||
skillsearch = skillsearch.lower().strip('\n').strip('\r')
|
||||
|
||||
results = []
|
||||
# search instance accounts
|
||||
for subdir, dirs, files in os.walk(baseDir + '/accounts/'):
|
||||
for f in files:
|
||||
tagsFilename = os.path.join(baseDir + '/tags', f)
|
||||
if not os.path.isfile(tagsFilename):
|
||||
if not f.endswith('.json'):
|
||||
continue
|
||||
# get last modified datetime
|
||||
modTimesinceEpoc = os.path.getmtime(tagsFilename)
|
||||
lastModifiedDate = datetime.fromtimestamp(modTimesinceEpoc)
|
||||
fileDaysSinceEpoch = (lastModifiedDate - datetime(1970, 1, 1)).days
|
||||
# check if the file was last modified today
|
||||
if fileDaysSinceEpoch != daysSinceEpoch:
|
||||
if '@' not in f:
|
||||
continue
|
||||
if f.startswith('inbox@'):
|
||||
continue
|
||||
actorFilename = os.path.join(subdir, f)
|
||||
actorJson = loadJson(actorFilename)
|
||||
if actorJson:
|
||||
if actorJson.get('id') and \
|
||||
actorJson.get('skills') and \
|
||||
actorJson.get('name') and \
|
||||
actorJson.get('icon'):
|
||||
actor = actorJson['id']
|
||||
for skillName, skillLevel in actorJson['skills'].items():
|
||||
skillName = skillName.lower()
|
||||
if not (skillName in skillsearch or
|
||||
skillsearch in skillName):
|
||||
continue
|
||||
skillLevelStr = str(skillLevel)
|
||||
if skillLevel < 100:
|
||||
skillLevelStr = '0' + skillLevelStr
|
||||
if skillLevel < 10:
|
||||
skillLevelStr = '0' + skillLevelStr
|
||||
indexStr = \
|
||||
skillLevelStr + ';' + actor + ';' + \
|
||||
actorJson['name'] + \
|
||||
';' + actorJson['icon']['url']
|
||||
if indexStr not in results:
|
||||
results.append(indexStr)
|
||||
if not instanceOnly:
|
||||
# search actor cache
|
||||
for subdir, dirs, files in os.walk(baseDir + '/cache/actors/'):
|
||||
for f in files:
|
||||
if not f.endswith('.json'):
|
||||
continue
|
||||
if '@' not in f:
|
||||
continue
|
||||
if f.startswith('inbox@'):
|
||||
continue
|
||||
actorFilename = os.path.join(subdir, f)
|
||||
cachedActorJson = loadJson(actorFilename)
|
||||
if cachedActorJson:
|
||||
if cachedActorJson.get('actor'):
|
||||
actorJson = cachedActorJson['actor']
|
||||
if actorJson.get('id') and \
|
||||
actorJson.get('skills') and \
|
||||
actorJson.get('name') and \
|
||||
actorJson.get('icon'):
|
||||
actor = actorJson['id']
|
||||
for skillName, skillLevel in \
|
||||
actorJson['skills'].items():
|
||||
skillName = skillName.lower()
|
||||
if not (skillName in skillsearch or
|
||||
skillsearch in skillName):
|
||||
continue
|
||||
skillLevelStr = str(skillLevel)
|
||||
if skillLevel < 100:
|
||||
skillLevelStr = '0' + skillLevelStr
|
||||
if skillLevel < 10:
|
||||
skillLevelStr = '0' + skillLevelStr
|
||||
indexStr = \
|
||||
skillLevelStr + ';' + actor + ';' + \
|
||||
actorJson['name'] + \
|
||||
';' + actorJson['icon']['url']
|
||||
if indexStr not in results:
|
||||
results.append(indexStr)
|
||||
|
||||
hashTagName = f.split('.')[0]
|
||||
if isBlockedHashtag(baseDir, hashTagName):
|
||||
continue
|
||||
if daysSinceEpochStr not in open(tagsFilename).read():
|
||||
continue
|
||||
with open(tagsFilename, 'r') as tagsFile:
|
||||
line = tagsFile.readline()
|
||||
lineCtr = 1
|
||||
tagCtr = 0
|
||||
maxLineCtr = 1
|
||||
while line:
|
||||
if ' ' not in line:
|
||||
line = tagsFile.readline()
|
||||
lineCtr += 1
|
||||
# don't read too many lines
|
||||
if lineCtr >= maxLineCtr:
|
||||
break
|
||||
continue
|
||||
postDaysSinceEpochStr = line.split(' ')[0]
|
||||
if not postDaysSinceEpochStr.isdigit():
|
||||
line = tagsFile.readline()
|
||||
lineCtr += 1
|
||||
# don't read too many lines
|
||||
if lineCtr >= maxLineCtr:
|
||||
break
|
||||
continue
|
||||
postDaysSinceEpoch = int(postDaysSinceEpochStr)
|
||||
if postDaysSinceEpoch < daysSinceEpoch:
|
||||
break
|
||||
if postDaysSinceEpoch == daysSinceEpoch:
|
||||
if tagCtr == 0:
|
||||
tagSwarm.append(hashTagName)
|
||||
tagCtr += 1
|
||||
results.sort(reverse=True)
|
||||
|
||||
line = tagsFile.readline()
|
||||
lineCtr += 1
|
||||
# don't read too many lines
|
||||
if lineCtr >= maxLineCtr:
|
||||
break
|
||||
cssFilename = baseDir + '/epicyon-profile.css'
|
||||
if os.path.isfile(baseDir + '/epicyon.css'):
|
||||
cssFilename = baseDir + '/epicyon.css'
|
||||
|
||||
if not tagSwarm:
|
||||
return ''
|
||||
tagSwarm.sort()
|
||||
tagSwarmStr = ''
|
||||
ctr = 0
|
||||
for tagName in tagSwarm:
|
||||
tagSwarmStr += \
|
||||
'<a href="' + actor + '/tags/' + tagName + \
|
||||
'" class="hashtagswarm">' + tagName + '</a>\n'
|
||||
ctr += 1
|
||||
tagSwarmHtml = tagSwarmStr.strip() + '\n'
|
||||
return tagSwarmHtml
|
||||
skillSearchForm = htmlHeaderWithExternalStyle(cssFilename)
|
||||
skillSearchForm += \
|
||||
'<center><h1>' + translate['Skills search'] + ': ' + \
|
||||
skillsearch + '</h1></center>'
|
||||
|
||||
if len(results) == 0:
|
||||
skillSearchForm += \
|
||||
'<center><h5>' + translate['No results'] + \
|
||||
'</h5></center>'
|
||||
else:
|
||||
skillSearchForm += '<center>'
|
||||
ctr = 0
|
||||
for skillMatch in results:
|
||||
skillMatchFields = skillMatch.split(';')
|
||||
if len(skillMatchFields) != 4:
|
||||
continue
|
||||
actor = skillMatchFields[1]
|
||||
actorName = skillMatchFields[2]
|
||||
avatarUrl = skillMatchFields[3]
|
||||
skillSearchForm += \
|
||||
'<div class="search-result""><a href="' + \
|
||||
actor + '/skills">'
|
||||
skillSearchForm += \
|
||||
'<img loading="lazy" src="' + avatarUrl + \
|
||||
'"/><span class="search-result-text">' + actorName + \
|
||||
'</span></a></div>'
|
||||
ctr += 1
|
||||
if ctr >= postsPerPage:
|
||||
break
|
||||
skillSearchForm += '</center>'
|
||||
skillSearchForm += htmlFooter()
|
||||
return skillSearchForm
|
||||
|
||||
|
||||
def htmlHistorySearch(cssCache: {}, translate: {}, baseDir: str,
|
||||
httpPrefix: str,
|
||||
nickname: str, domain: str,
|
||||
historysearch: str,
|
||||
postsPerPage: int, pageNumber: int,
|
||||
projectVersion: str,
|
||||
recentPostsCache: {},
|
||||
maxRecentPosts: int,
|
||||
session,
|
||||
wfRequest,
|
||||
personCache: {},
|
||||
port: int,
|
||||
YTReplacementDomain: str,
|
||||
showPublishedDateOnly: bool) -> str:
|
||||
"""Show a page containing search results for your post history
|
||||
"""
|
||||
if historysearch.startswith('!'):
|
||||
historysearch = historysearch[1:].strip()
|
||||
|
||||
historysearch = historysearch.lower().strip('\n').strip('\r')
|
||||
|
||||
boxFilenames = \
|
||||
searchBoxPosts(baseDir, nickname, domain,
|
||||
historysearch, postsPerPage)
|
||||
|
||||
cssFilename = baseDir + '/epicyon-profile.css'
|
||||
if os.path.isfile(baseDir + '/epicyon.css'):
|
||||
cssFilename = baseDir + '/epicyon.css'
|
||||
|
||||
historySearchForm = \
|
||||
htmlHeaderWithExternalStyle(cssFilename)
|
||||
|
||||
# add the page title
|
||||
historySearchForm += \
|
||||
'<center><h1>' + translate['Your Posts'] + '</h1></center>'
|
||||
|
||||
if len(boxFilenames) == 0:
|
||||
historySearchForm += \
|
||||
'<center><h5>' + translate['No results'] + \
|
||||
'</h5></center>'
|
||||
return historySearchForm
|
||||
|
||||
iconsPath = getIconsWebPath(baseDir)
|
||||
separatorStr = htmlPostSeparator(baseDir, None)
|
||||
|
||||
# ensure that the page number is in bounds
|
||||
if not pageNumber:
|
||||
pageNumber = 1
|
||||
elif pageNumber < 1:
|
||||
pageNumber = 1
|
||||
|
||||
# get the start end end within the index file
|
||||
startIndex = int((pageNumber - 1) * postsPerPage)
|
||||
endIndex = startIndex + postsPerPage
|
||||
noOfBoxFilenames = len(boxFilenames)
|
||||
if endIndex >= noOfBoxFilenames and noOfBoxFilenames > 0:
|
||||
endIndex = noOfBoxFilenames - 1
|
||||
|
||||
index = startIndex
|
||||
while index <= endIndex:
|
||||
postFilename = boxFilenames[index]
|
||||
if not postFilename:
|
||||
index += 1
|
||||
continue
|
||||
postJsonObject = loadJson(postFilename)
|
||||
if not postJsonObject:
|
||||
index += 1
|
||||
continue
|
||||
showIndividualPostIcons = True
|
||||
allowDeletion = False
|
||||
postStr = \
|
||||
individualPostAsHtml(True, recentPostsCache,
|
||||
maxRecentPosts,
|
||||
iconsPath, translate, None,
|
||||
baseDir, session, wfRequest,
|
||||
personCache,
|
||||
nickname, domain, port,
|
||||
postJsonObject,
|
||||
None, True, allowDeletion,
|
||||
httpPrefix, projectVersion,
|
||||
'search',
|
||||
YTReplacementDomain,
|
||||
showPublishedDateOnly,
|
||||
showIndividualPostIcons,
|
||||
showIndividualPostIcons,
|
||||
False, False, False)
|
||||
if postStr:
|
||||
historySearchForm += separatorStr + postStr
|
||||
index += 1
|
||||
|
||||
historySearchForm += htmlFooter()
|
||||
return historySearchForm
|
||||
|
||||
|
||||
def htmlHashtagSearch(cssCache: {},
|
||||
|
@ -711,220 +853,3 @@ def rssHashtagSearch(nickname: str, domain: str, port: int,
|
|||
break
|
||||
|
||||
return hashtagFeed + rss2TagFooter()
|
||||
|
||||
|
||||
def htmlSkillsSearch(cssCache: {}, translate: {}, baseDir: str,
|
||||
httpPrefix: str,
|
||||
skillsearch: str, instanceOnly: bool,
|
||||
postsPerPage: int) -> str:
|
||||
"""Show a page containing search results for a skill
|
||||
"""
|
||||
if skillsearch.startswith('*'):
|
||||
skillsearch = skillsearch[1:].strip()
|
||||
|
||||
skillsearch = skillsearch.lower().strip('\n').strip('\r')
|
||||
|
||||
results = []
|
||||
# search instance accounts
|
||||
for subdir, dirs, files in os.walk(baseDir + '/accounts/'):
|
||||
for f in files:
|
||||
if not f.endswith('.json'):
|
||||
continue
|
||||
if '@' not in f:
|
||||
continue
|
||||
if f.startswith('inbox@'):
|
||||
continue
|
||||
actorFilename = os.path.join(subdir, f)
|
||||
actorJson = loadJson(actorFilename)
|
||||
if actorJson:
|
||||
if actorJson.get('id') and \
|
||||
actorJson.get('skills') and \
|
||||
actorJson.get('name') and \
|
||||
actorJson.get('icon'):
|
||||
actor = actorJson['id']
|
||||
for skillName, skillLevel in actorJson['skills'].items():
|
||||
skillName = skillName.lower()
|
||||
if not (skillName in skillsearch or
|
||||
skillsearch in skillName):
|
||||
continue
|
||||
skillLevelStr = str(skillLevel)
|
||||
if skillLevel < 100:
|
||||
skillLevelStr = '0' + skillLevelStr
|
||||
if skillLevel < 10:
|
||||
skillLevelStr = '0' + skillLevelStr
|
||||
indexStr = \
|
||||
skillLevelStr + ';' + actor + ';' + \
|
||||
actorJson['name'] + \
|
||||
';' + actorJson['icon']['url']
|
||||
if indexStr not in results:
|
||||
results.append(indexStr)
|
||||
if not instanceOnly:
|
||||
# search actor cache
|
||||
for subdir, dirs, files in os.walk(baseDir + '/cache/actors/'):
|
||||
for f in files:
|
||||
if not f.endswith('.json'):
|
||||
continue
|
||||
if '@' not in f:
|
||||
continue
|
||||
if f.startswith('inbox@'):
|
||||
continue
|
||||
actorFilename = os.path.join(subdir, f)
|
||||
cachedActorJson = loadJson(actorFilename)
|
||||
if cachedActorJson:
|
||||
if cachedActorJson.get('actor'):
|
||||
actorJson = cachedActorJson['actor']
|
||||
if actorJson.get('id') and \
|
||||
actorJson.get('skills') and \
|
||||
actorJson.get('name') and \
|
||||
actorJson.get('icon'):
|
||||
actor = actorJson['id']
|
||||
for skillName, skillLevel in \
|
||||
actorJson['skills'].items():
|
||||
skillName = skillName.lower()
|
||||
if not (skillName in skillsearch or
|
||||
skillsearch in skillName):
|
||||
continue
|
||||
skillLevelStr = str(skillLevel)
|
||||
if skillLevel < 100:
|
||||
skillLevelStr = '0' + skillLevelStr
|
||||
if skillLevel < 10:
|
||||
skillLevelStr = '0' + skillLevelStr
|
||||
indexStr = \
|
||||
skillLevelStr + ';' + actor + ';' + \
|
||||
actorJson['name'] + \
|
||||
';' + actorJson['icon']['url']
|
||||
if indexStr not in results:
|
||||
results.append(indexStr)
|
||||
|
||||
results.sort(reverse=True)
|
||||
|
||||
cssFilename = baseDir + '/epicyon-profile.css'
|
||||
if os.path.isfile(baseDir + '/epicyon.css'):
|
||||
cssFilename = baseDir + '/epicyon.css'
|
||||
|
||||
skillSearchForm = htmlHeaderWithExternalStyle(cssFilename)
|
||||
skillSearchForm += \
|
||||
'<center><h1>' + translate['Skills search'] + ': ' + \
|
||||
skillsearch + '</h1></center>'
|
||||
|
||||
if len(results) == 0:
|
||||
skillSearchForm += \
|
||||
'<center><h5>' + translate['No results'] + \
|
||||
'</h5></center>'
|
||||
else:
|
||||
skillSearchForm += '<center>'
|
||||
ctr = 0
|
||||
for skillMatch in results:
|
||||
skillMatchFields = skillMatch.split(';')
|
||||
if len(skillMatchFields) != 4:
|
||||
continue
|
||||
actor = skillMatchFields[1]
|
||||
actorName = skillMatchFields[2]
|
||||
avatarUrl = skillMatchFields[3]
|
||||
skillSearchForm += \
|
||||
'<div class="search-result""><a href="' + \
|
||||
actor + '/skills">'
|
||||
skillSearchForm += \
|
||||
'<img loading="lazy" src="' + avatarUrl + \
|
||||
'"/><span class="search-result-text">' + actorName + \
|
||||
'</span></a></div>'
|
||||
ctr += 1
|
||||
if ctr >= postsPerPage:
|
||||
break
|
||||
skillSearchForm += '</center>'
|
||||
skillSearchForm += htmlFooter()
|
||||
return skillSearchForm
|
||||
|
||||
|
||||
def htmlHistorySearch(cssCache: {}, translate: {}, baseDir: str,
|
||||
httpPrefix: str,
|
||||
nickname: str, domain: str,
|
||||
historysearch: str,
|
||||
postsPerPage: int, pageNumber: int,
|
||||
projectVersion: str,
|
||||
recentPostsCache: {},
|
||||
maxRecentPosts: int,
|
||||
session,
|
||||
wfRequest,
|
||||
personCache: {},
|
||||
port: int,
|
||||
YTReplacementDomain: str,
|
||||
showPublishedDateOnly: bool) -> str:
|
||||
"""Show a page containing search results for your post history
|
||||
"""
|
||||
if historysearch.startswith('!'):
|
||||
historysearch = historysearch[1:].strip()
|
||||
|
||||
historysearch = historysearch.lower().strip('\n').strip('\r')
|
||||
|
||||
boxFilenames = \
|
||||
searchBoxPosts(baseDir, nickname, domain,
|
||||
historysearch, postsPerPage)
|
||||
|
||||
cssFilename = baseDir + '/epicyon-profile.css'
|
||||
if os.path.isfile(baseDir + '/epicyon.css'):
|
||||
cssFilename = baseDir + '/epicyon.css'
|
||||
|
||||
historySearchForm = \
|
||||
htmlHeaderWithExternalStyle(cssFilename)
|
||||
|
||||
# add the page title
|
||||
historySearchForm += \
|
||||
'<center><h1>' + translate['Your Posts'] + '</h1></center>'
|
||||
|
||||
if len(boxFilenames) == 0:
|
||||
historySearchForm += \
|
||||
'<center><h5>' + translate['No results'] + \
|
||||
'</h5></center>'
|
||||
return historySearchForm
|
||||
|
||||
iconsPath = getIconsWebPath(baseDir)
|
||||
separatorStr = htmlPostSeparator(baseDir, None)
|
||||
|
||||
# ensure that the page number is in bounds
|
||||
if not pageNumber:
|
||||
pageNumber = 1
|
||||
elif pageNumber < 1:
|
||||
pageNumber = 1
|
||||
|
||||
# get the start end end within the index file
|
||||
startIndex = int((pageNumber - 1) * postsPerPage)
|
||||
endIndex = startIndex + postsPerPage
|
||||
noOfBoxFilenames = len(boxFilenames)
|
||||
if endIndex >= noOfBoxFilenames and noOfBoxFilenames > 0:
|
||||
endIndex = noOfBoxFilenames - 1
|
||||
|
||||
index = startIndex
|
||||
while index <= endIndex:
|
||||
postFilename = boxFilenames[index]
|
||||
if not postFilename:
|
||||
index += 1
|
||||
continue
|
||||
postJsonObject = loadJson(postFilename)
|
||||
if not postJsonObject:
|
||||
index += 1
|
||||
continue
|
||||
showIndividualPostIcons = True
|
||||
allowDeletion = False
|
||||
postStr = \
|
||||
individualPostAsHtml(True, recentPostsCache,
|
||||
maxRecentPosts,
|
||||
iconsPath, translate, None,
|
||||
baseDir, session, wfRequest,
|
||||
personCache,
|
||||
nickname, domain, port,
|
||||
postJsonObject,
|
||||
None, True, allowDeletion,
|
||||
httpPrefix, projectVersion,
|
||||
'search',
|
||||
YTReplacementDomain,
|
||||
showPublishedDateOnly,
|
||||
showIndividualPostIcons,
|
||||
showIndividualPostIcons,
|
||||
False, False, False)
|
||||
if postStr:
|
||||
historySearchForm += separatorStr + postStr
|
||||
index += 1
|
||||
|
||||
historySearchForm += htmlFooter()
|
||||
return historySearchForm
|
||||
|
|
|
@ -7,23 +7,22 @@ __email__ = "bob@freedombone.net"
|
|||
__status__ = "Production"
|
||||
|
||||
import os
|
||||
from datetime import datetime
|
||||
import time
|
||||
from utils import removeIdEnding
|
||||
from follow import followerApprovalActive
|
||||
from person import isPersonSnoozed
|
||||
from happening import todaysEventsCheck
|
||||
from happening import thisWeeksEventsCheck
|
||||
from webapp_utils import getIconsWebPath
|
||||
from webapp_utils import htmlPostSeparator
|
||||
from webapp_utils import getBannerFile
|
||||
from webapp_utils import htmlHeaderWithExternalStyle
|
||||
from webapp_utils import htmlFooter
|
||||
from webapp_utils import sharesTimelineJson
|
||||
from webapp_utils import htmlHighlightLabel
|
||||
from webapp_post import preparePostFromHtmlCache
|
||||
from webapp_post import individualPostAsHtml
|
||||
from webapp_column_left import getLeftColumnContent
|
||||
from webapp_column_right import getRightColumnContent
|
||||
from webapp_headerbuttons import headerButtonsTimeline
|
||||
from posts import isModerator
|
||||
from posts import isEditor
|
||||
|
||||
|
@ -427,6 +426,8 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
|
|||
calendarImage, followApprovals,
|
||||
iconsAsButtons)
|
||||
|
||||
tlStr += ' <div class="timeline-posts">\n'
|
||||
|
||||
# second row of buttons for moderator actions
|
||||
if moderator and boxName == 'moderation':
|
||||
tlStr += \
|
||||
|
@ -580,9 +581,9 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
|
|||
|
||||
if currTlStr:
|
||||
itemCtr += 1
|
||||
tlStr += currTlStr
|
||||
if separatorStr:
|
||||
tlStr += separatorStr
|
||||
tlStr += currTlStr
|
||||
if boxName == 'tlmedia':
|
||||
tlStr += '</div>\n'
|
||||
|
||||
|
@ -598,6 +599,9 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
|
|||
translate['Page down'] + '"></a>\n' + \
|
||||
' </center>\n'
|
||||
|
||||
# end of timeline-posts
|
||||
tlStr += ' </div>\n'
|
||||
|
||||
# end of column-center
|
||||
tlStr += ' </td>\n'
|
||||
|
||||
|
@ -694,9 +698,10 @@ def htmlSharesTimeline(translate: {}, pageNumber: int, itemsPerPage: int,
|
|||
showRemoveButton = False
|
||||
if item['actor'] == actor:
|
||||
showRemoveButton = True
|
||||
timelineStr += separatorStr + \
|
||||
timelineStr += \
|
||||
htmlIndividualShare(actor, item, translate,
|
||||
showContactButton, showRemoveButton)
|
||||
timelineStr += separatorStr
|
||||
|
||||
if not lastPage:
|
||||
iconsPath = getIconsWebPath(baseDir)
|
||||
|
@ -712,332 +717,6 @@ def htmlSharesTimeline(translate: {}, pageNumber: int, itemsPerPage: int,
|
|||
return timelineStr
|
||||
|
||||
|
||||
def htmlHighlightLabel(label: str, highlight: bool) -> str:
|
||||
"""If the give text should be highlighted then return
|
||||
the appropriate markup.
|
||||
This is so that in shell browsers, like lynx, it's possible
|
||||
to see if the replies or DM button are highlighted.
|
||||
"""
|
||||
if not highlight:
|
||||
return label
|
||||
return '*' + str(label) + '*'
|
||||
|
||||
|
||||
def headerButtonsTimeline(defaultTimeline: str,
|
||||
boxName: str,
|
||||
pageNumber: int,
|
||||
translate: {},
|
||||
usersPath: str,
|
||||
mediaButton: str,
|
||||
blogsButton: str,
|
||||
newsButton: str,
|
||||
inboxButton: str,
|
||||
dmButton: str,
|
||||
newDM: str,
|
||||
repliesButton: str,
|
||||
newReply: str,
|
||||
minimal: bool,
|
||||
sentButton: str,
|
||||
sharesButtonStr: str,
|
||||
bookmarksButtonStr: str,
|
||||
eventsButtonStr: str,
|
||||
moderationButtonStr: str,
|
||||
newPostButtonStr: str,
|
||||
baseDir: str,
|
||||
nickname: str, domain: str,
|
||||
iconsPath: str,
|
||||
timelineStartTime,
|
||||
newCalendarEvent: bool,
|
||||
calendarPath: str,
|
||||
calendarImage: str,
|
||||
followApprovals: str,
|
||||
iconsAsButtons: bool) -> str:
|
||||
"""Returns the header at the top of the timeline, containing
|
||||
buttons for inbox, outbox, search, calendar, etc
|
||||
"""
|
||||
# start of the button header with inbox, outbox, etc
|
||||
tlStr = '<div class="containerHeader">\n'
|
||||
# first button
|
||||
if defaultTimeline == 'tlmedia':
|
||||
tlStr += \
|
||||
'<a href="' + usersPath + \
|
||||
'/tlmedia"><button class="' + \
|
||||
mediaButton + '"><span>' + translate['Media'] + \
|
||||
'</span></button></a>'
|
||||
elif defaultTimeline == 'tlblogs':
|
||||
tlStr += \
|
||||
'<a href="' + usersPath + \
|
||||
'/tlblogs"><button class="' + \
|
||||
blogsButton + '"><span>' + translate['Blogs'] + \
|
||||
'</span></button></a>'
|
||||
elif defaultTimeline == 'tlnews':
|
||||
tlStr += \
|
||||
'<a href="' + usersPath + \
|
||||
'/tlnews"><button class="' + \
|
||||
newsButton + '"><span>' + translate['Features'] + \
|
||||
'</span></button></a>'
|
||||
else:
|
||||
tlStr += \
|
||||
'<a href="' + usersPath + \
|
||||
'/inbox"><button class="' + \
|
||||
inboxButton + '"><span>' + \
|
||||
translate['Inbox'] + '</span></button></a>'
|
||||
|
||||
# if this is a news instance and we are viewing the news timeline
|
||||
newsHeader = False
|
||||
if defaultTimeline == 'tlnews' and boxName == 'tlnews':
|
||||
newsHeader = True
|
||||
|
||||
if not newsHeader:
|
||||
tlStr += \
|
||||
'<a href="' + usersPath + \
|
||||
'/dm"><button class="' + dmButton + \
|
||||
'"><span>' + htmlHighlightLabel(translate['DM'], newDM) + \
|
||||
'</span></button></a>'
|
||||
|
||||
tlStr += \
|
||||
'<a href="' + usersPath + '/tlreplies"><button class="' + \
|
||||
repliesButton + '"><span>' + \
|
||||
htmlHighlightLabel(translate['Replies'], newReply) + \
|
||||
'</span></button></a>'
|
||||
|
||||
# typically the media button
|
||||
if defaultTimeline != 'tlmedia':
|
||||
if not minimal and not newsHeader:
|
||||
tlStr += \
|
||||
'<a href="' + usersPath + \
|
||||
'/tlmedia"><button class="' + \
|
||||
mediaButton + '"><span>' + translate['Media'] + \
|
||||
'</span></button></a>'
|
||||
else:
|
||||
if not minimal:
|
||||
tlStr += \
|
||||
'<a href="' + usersPath + \
|
||||
'/inbox"><button class="' + \
|
||||
inboxButton+'"><span>' + translate['Inbox'] + \
|
||||
'</span></button></a>'
|
||||
|
||||
isFeaturesTimeline = \
|
||||
defaultTimeline == 'tlnews' and boxName == 'tlnews'
|
||||
|
||||
if not isFeaturesTimeline:
|
||||
# typically the blogs button
|
||||
# but may change if this is a blogging oriented instance
|
||||
if defaultTimeline != 'tlblogs':
|
||||
if not minimal and not isFeaturesTimeline:
|
||||
titleStr = translate['Blogs']
|
||||
if defaultTimeline == 'tlnews':
|
||||
titleStr = translate['Article']
|
||||
tlStr += \
|
||||
'<a href="' + usersPath + \
|
||||
'/tlblogs"><button class="' + \
|
||||
blogsButton + '"><span>' + titleStr + \
|
||||
'</span></button></a>'
|
||||
else:
|
||||
if not minimal:
|
||||
tlStr += \
|
||||
'<a href="' + usersPath + \
|
||||
'/inbox"><button class="' + \
|
||||
inboxButton + '"><span>' + translate['Inbox'] + \
|
||||
'</span></button></a>'
|
||||
|
||||
# typically the news button
|
||||
# but may change if this is a news oriented instance
|
||||
if defaultTimeline != 'tlnews':
|
||||
tlStr += \
|
||||
'<a href="' + usersPath + \
|
||||
'/tlnews"><button class="' + \
|
||||
newsButton + '"><span>' + translate['News'] + \
|
||||
'</span></button></a>'
|
||||
else:
|
||||
if not newsHeader:
|
||||
tlStr += \
|
||||
'<a href="' + usersPath + \
|
||||
'/inbox"><button class="' + \
|
||||
inboxButton + '"><span>' + translate['Inbox'] + \
|
||||
'</span></button></a>'
|
||||
|
||||
# show todays events buttons on the first inbox page
|
||||
happeningStr = ''
|
||||
if boxName == 'inbox' and pageNumber == 1:
|
||||
if todaysEventsCheck(baseDir, nickname, domain):
|
||||
now = datetime.now()
|
||||
|
||||
# happening today button
|
||||
if not iconsAsButtons:
|
||||
happeningStr += \
|
||||
'<a href="' + usersPath + '/calendar?year=' + \
|
||||
str(now.year) + '?month=' + str(now.month) + \
|
||||
'?day=' + str(now.day) + '">' + \
|
||||
'<button class="buttonevent">' + \
|
||||
translate['Happening Today'] + '</button></a>'
|
||||
else:
|
||||
happeningStr += \
|
||||
'<a href="' + usersPath + '/calendar?year=' + \
|
||||
str(now.year) + '?month=' + str(now.month) + \
|
||||
'?day=' + str(now.day) + '">' + \
|
||||
'<button class="button">' + \
|
||||
translate['Happening Today'] + '</button></a>'
|
||||
|
||||
# happening this week button
|
||||
if thisWeeksEventsCheck(baseDir, nickname, domain):
|
||||
if not iconsAsButtons:
|
||||
happeningStr += \
|
||||
'<a href="' + usersPath + \
|
||||
'/calendar"><button class="buttonevent">' + \
|
||||
translate['Happening This Week'] + '</button></a>'
|
||||
else:
|
||||
happeningStr += \
|
||||
'<a href="' + usersPath + \
|
||||
'/calendar"><button class="button">' + \
|
||||
translate['Happening This Week'] + '</button></a>'
|
||||
else:
|
||||
# happening this week button
|
||||
if thisWeeksEventsCheck(baseDir, nickname, domain):
|
||||
if not iconsAsButtons:
|
||||
happeningStr += \
|
||||
'<a href="' + usersPath + \
|
||||
'/calendar"><button class="buttonevent">' + \
|
||||
translate['Happening This Week'] + '</button></a>'
|
||||
else:
|
||||
happeningStr += \
|
||||
'<a href="' + usersPath + \
|
||||
'/calendar"><button class="button">' + \
|
||||
translate['Happening This Week'] + '</button></a>'
|
||||
|
||||
if not newsHeader:
|
||||
# button for the outbox
|
||||
tlStr += \
|
||||
'<a href="' + usersPath + \
|
||||
'/outbox"><button class="' + \
|
||||
sentButton + '"><span>' + translate['Outbox'] + \
|
||||
'</span></button></a>'
|
||||
|
||||
# add other buttons
|
||||
tlStr += \
|
||||
sharesButtonStr + bookmarksButtonStr + eventsButtonStr + \
|
||||
moderationButtonStr + happeningStr + newPostButtonStr
|
||||
|
||||
if not newsHeader:
|
||||
if not iconsAsButtons:
|
||||
# the search icon
|
||||
tlStr += \
|
||||
'<a class="imageAnchor" href="' + usersPath + \
|
||||
'/search"><img loading="lazy" src="/' + \
|
||||
iconsPath + '/search.png" title="' + \
|
||||
translate['Search and follow'] + '" alt="| ' + \
|
||||
translate['Search and follow'] + \
|
||||
'" class="timelineicon"/></a>'
|
||||
else:
|
||||
# the search button
|
||||
tlStr += \
|
||||
'<a href="' + usersPath + \
|
||||
'/search"><button class="button">' + \
|
||||
'<span>' + translate['Search'] + \
|
||||
'</span></button></a>'
|
||||
|
||||
# benchmark 5
|
||||
timeDiff = int((time.time() - timelineStartTime) * 1000)
|
||||
if timeDiff > 100:
|
||||
print('TIMELINE TIMING ' + boxName + ' 5 = ' + str(timeDiff))
|
||||
|
||||
# the calendar button
|
||||
if not isFeaturesTimeline:
|
||||
calendarAltText = translate['Calendar']
|
||||
if newCalendarEvent:
|
||||
# indicate that the calendar icon is highlighted
|
||||
calendarAltText = '*' + calendarAltText + '*'
|
||||
if not iconsAsButtons:
|
||||
tlStr += \
|
||||
' <a class="imageAnchor" href="' + \
|
||||
usersPath + calendarPath + \
|
||||
'"><img loading="lazy" src="/' + iconsPath + '/' + \
|
||||
calendarImage + '" title="' + translate['Calendar'] + \
|
||||
'" alt="| ' + calendarAltText + \
|
||||
'" class="timelineicon"/></a>\n'
|
||||
else:
|
||||
tlStr += \
|
||||
'<a href="' + usersPath + calendarPath + \
|
||||
'"><button class="button">' + \
|
||||
'<span>' + translate['Calendar'] + \
|
||||
'</span></button></a>'
|
||||
|
||||
if not newsHeader:
|
||||
# the show/hide button, for a simpler header appearance
|
||||
if not iconsAsButtons:
|
||||
tlStr += \
|
||||
' <a class="imageAnchor" href="' + \
|
||||
usersPath + '/minimal' + \
|
||||
'"><img loading="lazy" src="/' + iconsPath + \
|
||||
'/showhide.png" title="' + translate['Show/Hide Buttons'] + \
|
||||
'" alt="| ' + translate['Show/Hide Buttons'] + \
|
||||
'" class="timelineicon"/></a>\n'
|
||||
else:
|
||||
tlStr += \
|
||||
'<a href="' + usersPath + '/minimal' + \
|
||||
'"><button class="button">' + \
|
||||
'<span>' + translate['Show/Hide Buttons'] + \
|
||||
'</span></button></a>'
|
||||
|
||||
if newsHeader:
|
||||
tlStr += \
|
||||
'<a href="' + usersPath + '/inbox">' + \
|
||||
'<button class="button">' + \
|
||||
'<span>' + translate['User'] + '</span></button></a>'
|
||||
|
||||
# the newswire button to show right column links
|
||||
if not iconsAsButtons:
|
||||
tlStr += \
|
||||
'<a class="imageAnchorMobile" href="' + \
|
||||
usersPath + '/newswiremobile">' + \
|
||||
'<img loading="lazy" src="/' + iconsPath + \
|
||||
'/newswire.png" title="' + translate['News'] + \
|
||||
'" alt="| ' + translate['News'] + \
|
||||
'" class="timelineicon"/></a>'
|
||||
else:
|
||||
# NOTE: deliberately no \n at end of line
|
||||
tlStr += \
|
||||
'<a href="' + \
|
||||
usersPath + '/newswiremobile' + \
|
||||
'"><button class="buttonMobile">' + \
|
||||
'<span>' + translate['Newswire'] + \
|
||||
'</span></button></a>'
|
||||
|
||||
# the links button to show left column links
|
||||
if not iconsAsButtons:
|
||||
tlStr += \
|
||||
'<a class="imageAnchorMobile" href="' + \
|
||||
usersPath + '/linksmobile">' + \
|
||||
'<img loading="lazy" src="/' + iconsPath + \
|
||||
'/links.png" title="' + translate['Edit Links'] + \
|
||||
'" alt="| ' + translate['Edit Links'] + \
|
||||
'" class="timelineicon"/></a>'
|
||||
else:
|
||||
# NOTE: deliberately no \n at end of line
|
||||
tlStr += \
|
||||
'<a href="' + \
|
||||
usersPath + '/linksmobile' + \
|
||||
'"><button class="buttonMobile">' + \
|
||||
'<span>' + translate['Links'] + \
|
||||
'</span></button></a>'
|
||||
|
||||
if newsHeader:
|
||||
tlStr += \
|
||||
'<a href="' + usersPath + '/editprofile">' + \
|
||||
'<button class="buttonDesktop">' + \
|
||||
'<span>' + translate['Settings'] + '</span></button></a>'
|
||||
|
||||
if not iconsAsButtons:
|
||||
# end of headericons div
|
||||
tlStr += '</div>'
|
||||
|
||||
if not newsHeader:
|
||||
tlStr += followApprovals
|
||||
# end of the button header with inbox, outbox, etc
|
||||
tlStr += ' </div>\n'
|
||||
return tlStr
|
||||
|
||||
|
||||
def htmlShares(cssCache: {}, defaultTimeline: str,
|
||||
recentPostsCache: {}, maxRecentPosts: int,
|
||||
translate: {}, pageNumber: int, itemsPerPage: int,
|
||||
|
|
|
@ -728,13 +728,15 @@ def htmlPostSeparator(baseDir: str, column: str) -> str:
|
|||
iconsPath = getIconsWebPath(baseDir)
|
||||
theme = getConfigParam(baseDir, 'theme')
|
||||
filename = 'separator.png'
|
||||
separatorClass = "postSeparatorImage"
|
||||
if column:
|
||||
separatorClass = "postSeparatorImage" + column.title()
|
||||
filename = 'separator_' + column + '.png'
|
||||
separatorImageFilename = baseDir + '/theme/' + theme + '/icons/' + filename
|
||||
separatorStr = ''
|
||||
if os.path.isfile(separatorImageFilename):
|
||||
separatorStr = \
|
||||
'<div class="postSeparatorImage"><center>' + \
|
||||
'<div class="' + separatorClass + '"><center>' + \
|
||||
'<img src="/' + iconsPath + '/' + filename + '"/>' + \
|
||||
'</center></div>\n'
|
||||
return separatorStr
|
||||
|
@ -808,3 +810,14 @@ def headerButtonsFrontScreen(translate: {},
|
|||
headerStr + \
|
||||
' </div>\n'
|
||||
return headerStr
|
||||
|
||||
|
||||
def htmlHighlightLabel(label: str, highlight: bool) -> str:
|
||||
"""If the given text should be highlighted then return
|
||||
the appropriate markup.
|
||||
This is so that in shell browsers, like lynx, it's possible
|
||||
to see if the replies or DM button are highlighted.
|
||||
"""
|
||||
if not highlight:
|
||||
return label
|
||||
return '*' + str(label) + '*'
|
||||
|
|