Adding customisations

main
Paco Hope 2023-12-14 09:09:10 -05:00
parent 9fb3e926c6
commit 82313462fe
36 changed files with 2062 additions and 0 deletions

BIN
static/John Mastodon AI.xcf Normal file

Binary file not shown.

147
static/css/alata1.css Normal file
View File

@ -0,0 +1,147 @@
@font-face {
font-family: 'Alata';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(/fonts/PbytFmztEwbIofe6.ttf) format('truetype');
}
@font-face {
font-family: 'Lora';
font-style: italic;
font-weight: 400;
font-display: swap;
src: url(/fonts/0QI8MX1D_JOuMw_hLdO6T2wV9KnW-MoFkqg.ttf) format('truetype');
}
@font-face {
font-family: 'Lora';
font-style: italic;
font-weight: 500;
font-display: swap;
src: url(/fonts/0QI8MX1D_JOuMw_hLdO6T2wV9KnW-PgFkqg.ttf) format('truetype');
}
@font-face {
font-family: 'Lora';
font-style: italic;
font-weight: 600;
font-display: swap;
src: url(/fonts/0QI8MX1D_JOuMw_hLdO6T2wV9KnW-BQCkqg.ttf) format('truetype');
}
@font-face {
font-family: 'Lora';
font-style: italic;
font-weight: 700;
font-display: swap;
src: url(/fonts/0QI8MX1D_JOuMw_hLdO6T2wV9KnW-C0Ckqg.ttf) format('truetype');
}
@font-face {
font-family: 'Lora';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(/fonts/0QI6MX1D_JOuGQbT0gvTJPa787weuyJG.ttf) format('truetype');
}
@font-face {
font-family: 'Lora';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(/fonts/0QI6MX1D_JOuGQbT0gvTJPa787wsuyJG.ttf) format('truetype');
}
@font-face {
font-family: 'Lora';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url(/fonts/0QI6MX1D_JOuGQbT0gvTJPa787zAvCJG.ttf) format('truetype');
}
@font-face {
font-family: 'Lora';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url(/fonts/0QI6MX1D_JOuGQbT0gvTJPa787z5vCJG.ttf) format('truetype');
}
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 100;
font-display: swap;
src: url(/fonts/KFOiCnqEu92Fr1Mu51QrIzc.ttf) format('truetype');
}
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 300;
font-display: swap;
src: url(/fonts/KFOjCnqEu92Fr1Mu51TjARc9.ttf) format('truetype');
}
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 400;
font-display: swap;
src: url(/fonts/KFOkCnqEu92Fr1Mu52xP.ttf) format('truetype');
}
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 500;
font-display: swap;
src: url(/fonts/KFOjCnqEu92Fr1Mu51S7ABc9.ttf) format('truetype');
}
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 700;
font-display: swap;
src: url(/fonts/KFOjCnqEu92Fr1Mu51TzBhc9.ttf) format('truetype');
}
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 900;
font-display: swap;
src: url(/fonts/KFOjCnqEu92Fr1Mu51TLBBc9.ttf) format('truetype');
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 100;
font-display: swap;
src: url(/fonts/KFOkCnqEu92Fr1MmgWxP.ttf) format('truetype');
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 300;
font-display: swap;
src: url(/fonts/KFOlCnqEu92Fr1MmSU5vAw.ttf) format('truetype');
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(/fonts/KFOmCnqEu92Fr1Me5Q.ttf) format('truetype');
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(/fonts/KFOlCnqEu92Fr1MmEU9vAw.ttf) format('truetype');
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url(/fonts/KFOlCnqEu92Fr1MmWUlvAw.ttf) format('truetype');
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 900;
font-display: swap;
src: url(/fonts/KFOlCnqEu92Fr1MmYUtvAw.ttf) format('truetype');
}

574
static/css/index.css Normal file
View File

@ -0,0 +1,574 @@
/* Animation */
@keyframes fade-in {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes fade-bottom {
0% {
transform: translateY(50px);
opacity: 0;
}
100% {
transform: translateY(0);
opacity: 1;
}
}
@keyframes fade-left {
0% {
transform: translateX(-10px);
opacity: 0;
}
100% {
transform: translateX(0);
opacity: 1;
}
}
@keyframes fade-right {
0% {
transform: translateX(20px);
opacity: 0;
}
100% {
transform: translateX(0);
opacity: 1;
}
}
/* Header animation */
@keyframes fade-up {
0% {
transform: translateY(-10px);
opacity: 0;
}
100% {
transform: translateY(0);
opacity: 1;
}
}
header .navbar.animate {
animation: fade-up 0.5s ease-in;
}
/* ToolTip */
.tooltip {
line-height: 1rem;
border-radius: .5rem !important;
}
.tooltip-inner {
line-height: 1rem;
}
.tooltip .tooltip-arrow {
visibility: hidden !important;
}
/* hero */
#hero {
min-height: 100vh;
line-height: 2rem;
max-width: 100%;
}
#hero .content.animate {
animation: fade-left 1s ease-out;
}
#hero .hero-bottom-svg {
opacity: 0.5;
position: absolute;
bottom: -50px;
left: -150px;
}
#hero .subtitle {
font-size: clamp(14px,5vw,16px);
opacity: 0.6;
}
#hero h2 {
font-size: clamp(40px, 8vw, 80px);
color: var(--primary-color) !important;
}
#hero h3 {
font-size: clamp(40px, 8vw, 60px);
/* color: var(--primary-color) !important; */
opacity: 0.5;
}
#hero p {
margin: 20px 0px 0px;
max-width: 640px;
opacity: 0.8;
}
#hero .image img {
box-shadow:0px 8px 56px rgba(15, 80, 100, 0.16);
padding: 0;
border: 3px solid var(--secondary-color);
border-radius: 1rem;
}
#hero .image.animate img {
animation: fade-in 1s ease-out;
transition: box-shadow 0.3s;
}
#hero .image img:hover {
cursor: pointer;
}
#hero .image.animate img:hover {
box-shadow: 0 0 11px rgb(15 80 100 / 20%);
filter: contrast(1.2);
cursor: pointer;
}
#hero a.btn.social-icon {
color: var(--primary-color) !important;
line-height: 0%;
border-radius: 50%;
margin-top: 50px;
padding: 0.7rem;
border: 1px solid var(--primary-color);
transition: none;
}
#hero a.btn.social-icon img {
width: 1em;
}
#hero a.btn.social-icon:hover {
opacity: 0.8;
}
#hero a.btn {
margin-top: 50px;
padding: 0.7rem 1.75rem;
border: 1px solid var(--primary-color);
color: var(--text-color) !important;
border-radius: .75rem;
transition: none;
}
#hero a.btn:focus {
box-shadow: none;
}
#hero a.btn:hover {
background-color: var(--secondary-color) !important;
color: var(--text-color) !important;
opacity: 0.9;
}
#hero a.btn.social-icon:hover {
background-color: var(--background-color) !important;
opacity: 0.7;
}
#hero .hero-content > a {
display: inline-block;
color: var(--primary-color) !important;
}
#hero .hero-content > a::after {
content: "";
display: block;
width: 0px;
height: 2px;
bottom: 0.37em;
background-color: var(--primary-color);
transition: all 0.25s cubic-bezier(0.645,0.045,0.355,1);
opacity: 0.5;
}
#hero .hero-content > a:hover::after, #hero .hero-content > a:focus::after, #hero .hero-content > a:active::after {
width: 100%;
}
/* about me */
#about h3 {
color: var(--text-secondary-color) !important;
}
#about .image img {
box-shadow: 0px 8px 56px rgba(15, 80, 100, 0.16);
transition: box-shadow 0.3s;
padding: 0;
border: 0;
}
#about .image img:hover {
box-shadow: 0 0 11px rgb(15 80 100 / 20%);
}
#about ul {
display: grid;
grid-template-columns: repeat(2, minmax(140px, 200px));
gap: 0px 10px;
padding: 0px;
margin: 20px 0px 0px;
overflow: hidden;
list-style: none;
}
#about ul li {
position: relative;
margin-bottom: 10px;
padding-left: 20px;
}
#about ul li::before {
content: "▹";
color: var(--primary-color);
position: absolute;
left: 0px;
}
#about .content {
font-weight: 500;
opacity: 0.9 !important;
line-height: 1.7rem !important;
}
#about a {
display: inline-block;
color: var(--primary-color) !important;
}
#about a::after {
content: "";
display: block;
width: 0px;
height: 2px;
bottom: 0.37em;
background-color: var(--primary-color);
transition: all 0.25s cubic-bezier(0.645,0.045,0.355,1);
opacity: 0.5;
}
#about a:hover::after, #about a:focus::after, #about a:active::after {
width: 100%;
}
/* experience */
#experience h3 {
color: var(--text-secondary-color) !important;
}
#experience * {
background-color: transparent !important;
}
#experience .tab-pane > * {
opacity: 0.9;
}
#experience .tab-pane small {
opacity: 0.8;
}
#experience .tab-pane ul {
padding-top: 1%;
padding-bottom: 1%;
}
#experience .experience-container .tab-content > .tab-pane p {
padding: 1% 0;
margin: 0;
}
#experience .experience-container {
background-color: var(--secondary-color) !important;
border-radius: .75rem;
box-shadow: 0px 8px 56px rgb(15 80 100 / 16%);
}
#experience .nav-item .nav-link {
color: var(--text-color) !important;
border-bottom: 2px solid transparent;
border-radius: 0%;
transition: none;
cursor: pointer;
}
#experience .nav-item .nav-link.active {
color: var(--text-color) !important;
border-bottom: 2px solid var(--primary-color);
opacity: 0.8;
}
#experience .nav-item .nav-link.active:hover {
transition: none !important;
}
#experience .nav-item .nav-link:hover {
border-bottom: 2px solid var(--primary-color);
opacity: 0.8;
}
#experience a {
opacity: 0.9;
display: inline-block;
color: var(--primary-color) !important;
}
#experience a::after {
content: "";
display: block;
width: 0px;
height: 2px;
bottom: 0.37em;
background-color: var(--primary-color);
transition: all 0.25s cubic-bezier(0.645,0.045,0.355,1);
opacity: 0.5;
}
#experience a:hover::after, #experience a:focus::after, #experience a:active::after {
width: 100%;
}
#experience .experience-container .tab-content .tab-pane ul {
overflow: hidden;
list-style: none;
margin-bottom: 0;
}
#experience .experience-container .tab-content .tab-pane ul li {
position: relative;
margin-bottom: 10px;
padding-left: 20px;
}
#experience .experience-container .tab-content .tab-pane ul li::before {
content: "▹";
color: var(--primary-color);
position: absolute;
left: 0px;
}
#experience .experience-container .tab-content .tab-pane .featuredLink a::after {
display: block;
width: auto;
height: auto;
bottom: 0em;
background-color: transparent;
transition: none;
opacity: 1;
}
#experience .experience-container .tab-content .tab-pane .featuredLink a.btn {
border: 1px solid var(--primary-color);
border-radius: .75rem;
transition: none;
}
#experience .experience-container .tab-content .tab-pane .featuredLink a.btn:focus {
box-shadow: none;
}
#experience .experience-container .tab-content .tab-pane .featuredLink a.btn:hover {
color: var(--text-color) !important;
opacity: 0.7;
}
/* Education */
#education .container > h3 {
color: var(--text-secondary-color) !important;
}
#education .row .index {
opacity: 0.8;
padding: 13px 20px;
line-height: 0%;
border-radius: 50%;
max-height: 50px;
z-index: 2;
background-color: var(--primary-color) !important;
color: var(--secondary-color) !important;
font-weight: bold;
}
#education .card * {
background-color: var(--secondary-color) !important;
}
#education .card {
border-radius: 1.5rem;
box-shadow: 0px 8px 56px rgb(15 80 100 / 16%);
border: 2px solid var(--text-secondary-color) !important;
transition: box-shadow .2s linear,opacity .2s linear;
transition: transform 0.2s;
}
#education .card .card-body {
border-radius: 1.5rem;
padding: 2rem;
}
@media all and (max-width:768px) {
#education .card .card-body {
padding: 2rem 1rem;
}
}
#education .card:hover {
transition: 0.3s;
box-shadow: 0 4px 11px rgb(15 80 100 / 16%);
border: 2px solid var(--primary-color) !important;
}
#education .card .card-body .education-content a {
color: var(--primary-color) !important;
opacity: 0.9;
}
#education .card .card-body > a h6 {
display: inline-block;
color: var(--primary-color) !important;
}
#education .card .card-body > a h6::after {
content: "";
display: block;
width: 0px;
height: 2px;
bottom: 0.37em;
background-color: var(--primary-color);
transition: all 0.25s cubic-bezier(0.645,0.045,0.355,1);
opacity: 0.5;
}
#education .card .card-body > a h6:hover::after, #education .card .card-body > a h6:focus::after, #education .card .card-body > a h6:active::after {
width: 100%;
}
#education .card .card-body a.btn {
opacity: 0.9;
border: 1px solid var(--primary-color) !important;
color: var(--text-color) !important;
border-radius: .75rem;
box-shadow: none;
transition: none;
}
#education .card .card-body a.btn:hover {
opacity: 0.8;
}
/* achievements */
#achievements a {
text-decoration: none;
}
#achievements h3 {
color: var(--text-secondary-color) !important;
}
#achievements .card {
cursor: context-menu;
background-color: var(--secondary-color) !important;
border-radius: 1rem;
box-shadow: 0 0 36px rgba(0,0,0,0.1);
/* transform: translate3d(0, 0, 0); */
transition: box-shadow .2s linear,opacity .2s linear;
border: 2px solid transparent;
}
#achievements a.card {
cursor: alias;
}
#achievements .card h5 {
color: var(--text-color) !important;
opacity: 0.9;
}
#achievements .card:hover {
border: 2px solid var(--text-color);
transition: .3s;
}
#achievements .card:focus {
border: 2px solid var(--text-color);
transition: .3s;
}
#achievements .card-text {
background-color: var(--secondary-color) !important;
color: var(--text-secondary-color) !important;
}
#achievements img {
border-radius: 0.7rem;
}
/* contact */
#contact h3 {
color: var(--text-secondary-color) !important;
}
#contact .btn {
transition: none;
transition: opacity 0.3s;
border-radius: .5rem !important;
border-color: var(--primary-color) !important;
background-color: var(--secondary-color) !important;
color: var(--text-color) !important;
}
#contact .btn:hover {
opacity: .7;
}
#contact .btn:focus {
box-shadow: none !important;
}
#contact form .form-control {
background-color: var(--secondary-color);
color: var(--text-color);
border-radius: .7rem;
border: 1px solid var(--text-secondary-color);
box-shadow: 0px 8px 56px rgb(15 80 100 / 5%);
}
#contact-form-status {
position: fixed;
bottom: 10px;
right: 10px;
z-index: 1;
transform: translate3d(0, 0, 0);
}
#contact-form-status svg {
height: 18px;
width: 18px;
}
#contact-form-status button {
border-radius: 50%;
border: none;
background-color: white;
padding: 0.5rem;
margin-left: 0.5rem;
box-shadow: 0px 8px 56px rgb(15 80 100 / 5%);
font-size: .6rem !important;
}
#contact-form-status .alert {
border-radius: 0.5rem;
box-shadow: 0px 8px 56px rgb(15 80 100 / 5%);
padding: .5rem 1rem;
}

View File

@ -0,0 +1,393 @@
/* Mastodon embed feed timeline v3.12.0 */
/* More info at: */
/* https://gitlab.com/idotj/mastodon-embed-feed-timeline */
/* Variables */
:root {
--text-max-lines: none;
}
/* Theme colors */
:root,
html[data-theme="light"] {
--bg-color: #fff;
--bg-hover-color: #d9e1e8;
--line-gray-color: #c0cdd9;
--contrast-gray-color: #606984;
--content-text: #000;
--link-color: #3a3bff;
--error-text-color: #8b0000;
}
html[data-theme="dark"] {
--bg-color: #282c37;
--bg-hover-color: #313543;
--line-gray-color: #393f4f;
--contrast-gray-color: #606984;
--content-text: #fff;
--link-color: #8c8dff;
--error-text-color: #fe6c6c;
}
/* Main container */
.mt-container {
height: 100%;
overflow-y: auto;
position: relative;
background-color: var(--bg-color);
scrollbar-color: var(--line-gray-color) var(--bg-color);
scrollbar-width: thin;
}
.mt-container::-webkit-scrollbar {
width: 0.25rem;
height: 0.25rem;
}
.mt-container::-webkit-scrollbar-thumb {
background-color: var(--line-gray-color);
border: none;
border-radius: 3rem;
}
.mt-container::-webkit-scrollbar-thumb:hover,
.mt-container::-webkit-scrollbar-thumb:active {
background-color: var(--line-gray-color);
}
.mt-container::-webkit-scrollbar-track {
background-color: var(--bg-color);
border: none;
border-radius: 0;
}
.mt-container::-webkit-scrollbar-track:hover,
.mt-container::-webkit-scrollbar-track:active,
.mt-container::-webkit-scrollbar-corner {
background-color: var(--bg-color);
}
.mt-container a:link,
.mt-container a:active,
.mt-container a {
text-decoration: none;
color: var(--link-color);
}
.mt-container a:not(.mt-toot-preview):hover {
text-decoration: underline;
}
.mt-body {
padding: 1rem clamp(0.25rem, 4vw, 1.5rem);
white-space: pre-wrap;
word-wrap: break-word;
}
.mt-body .invisible {
font-size: 0;
line-height: 0;
display: inline-block;
width: 0;
height: 0;
position: absolute;
}
/* Toot container */
.mt-toot {
margin: 0.25rem;
padding: 0rem 0.5rem;
position: relative;
min-height: 3.75rem;
background-color: transparent;
border-bottom: 1px solid var(--line-gray-color);
}
.mt-toot:hover,
.mt-toot:focus {
cursor: pointer;
background-color: var(--bg-hover-color);
}
.mt-toot p:last-child {
margin-bottom: 0;
}
/* User avatar */
.mt-toot-avatar {
margin-right: 0.75rem;
}
.mt-toot-avatar-standard {
width: 2.25rem;
height: 2.25rem;
}
.mt-toot-avatar-boosted {
width: 3rem;
height: 3rem;
position: relative;
}
.mt-toot-avatar-image-big img {
aspect-ratio: 1/1;
width: 2.25rem;
height: 2.25rem;
border-radius: 0.25rem;
overflow: hidden;
}
.mt-toot-avatar-image-small img {
aspect-ratio: 1/1;
width: 1.5rem;
height: 1.5rem;
top: 1.5rem;
left: 1.5rem;
position: absolute;
border-radius: 0.25rem;
overflow: hidden;
}
/* User name and date */
.mt-toot-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 0rem;
}
.mt-toot-header-user {
font-weight: 600;
margin-top: 0rem;
padding-right: 1rem;
}
.mt-toot-header-user > a {
display: flex;
align-items: flex-start;
color: var(--content-text) !important;
overflow-wrap: anywhere;
}
.mt-toot-header-date {
font-size: 0.75rem;
text-align: right;
margin: 0.5rem 0 0 auto;
}
.mt-toot-header-date > a {
color: var(--contrast-gray-color) !important;
}
/* Text */
.mt-toot-text {
margin-bottom: 0rem;
color: var(--content-text);
}
.mt-toot-text .spoiler-btn {
display: inline-block;
}
.mt-toot-text .spoiler-text-hidden {
display: none;
}
.mt-toot-text.truncate {
display: -webkit-box;
overflow: hidden;
-webkit-line-clamp: var(--text-max-lines);
-webkit-box-orient: vertical;
}
.mt-toot-text:not(.truncate) .ellipsis::after {
content: "...";
}
.mt-toot-text blockquote {
border-left: 0.25rem solid var(--line-gray-color);
margin-left: 0;
padding-left: 0.5rem;
}
.mt-toot-header-user .custom-emoji,
.mt-toot-text .custom-emoji {
height: 0rem;
min-width: 1.5rem;
margin-bottom: -0.25rem;
width: auto;
}
/* Poll */
.mt-toot-poll {
margin-bottom: 1rem;
color: var(--content-text);
}
.mt-toot-poll ul {
list-style: none;
padding: 0;
margin: 0;
}
.mt-toot-poll ul li {
font-size: 0.9rem;
margin-bottom: 0rem;
}
.mt-toot-poll.mt-toot-poll-expired ul li {
color: var(--contrast-gray-color);
}
.mt-toot-poll ul li:not(:last-child) {
margin-bottom: 0rem;
}
.mt-toot-poll ul li:before {
content: "◯";
padding-right: 0.5rem;
}
.mt-toot-poll.mt-toot-poll-expired ul li:before {
content: "";
padding-right: 0;
}
/* Medias */
.mt-toot-media {
overflow: hidden;
margin-bottom: 1rem;
}
.mt-toot-media > .spoiler-btn {
position: absolute;
top: 50%;
left: 50%;
z-index: 1;
transform: translate(-50%, -50%);
}
.mt-toot-media-spoiler > img {
filter: blur(2rem);
}
.img-ratio14_7 {
position: relative;
padding-top: 56.95%;
width: 100%;
}
.img-ratio14_7 > img {
width: 100%;
height: auto;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
color: var(--content-text);
}
/* Preview link */
.mt-toot-preview {
min-height: 4rem;
display: flex;
flex-direction: row;
border: 1px solid var(--line-gray-color);
border-radius: 0.5rem;
color: var(--link-color);
font-size: 0.8rem;
margin: 1rem 0;
overflow: hidden;
}
.mt-toot-preview-image {
width: 40%;
align-self: stretch;
}
.mt-toot-preview-image img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
color: var(--content-text);
}
.mt-toot-preview-noImage {
width: 40%;
font-size: 1rem;
align-self: center;
text-align: center;
}
.mt-toot-preview-content {
width: 60%;
display: flex;
align-self: center;
flex-direction: column;
padding: 0px;
gap: 0.5rem;
}
.mt-toot-preview-title {
font-weight: 600;
}
/* Spoiler button */
.spoiler-btn {
border-radius: 2px;
background-color: var(--line-gray-color);
border: 0;
color: var(--content-text);
font-weight: 700;
font-size: 0.7rem;
padding: 0 0.35rem;
text-transform: uppercase;
line-height: 1.25rem;
cursor: pointer;
vertical-align: top;
}
/* Counter bar */
.mt-toot-counter-bar {
display: flex;
min-width: 6rem;
max-width: 40rem;
justify-content: space-between;
color: var(--contrast-gray-color);
}
.mt-toot-counter-bar-replies,
.mt-toot-counter-bar-reblog,
.mt-toot-counter-bar-favorites {
display: flex;
font-size: 0.75rem;
gap: 0.25rem;
align-items: center;
opacity: 0.5;
}
.mt-toot-counter-bar-replies > svg,
.mt-toot-counter-bar-reblog > svg,
.mt-toot-counter-bar-favorites > svg {
width: 1rem;
fill: var(--contrast-gray-color);
}
/* Error */
.mt-error {
position: absolute;
display: flex;
flex-direction: column;
height: calc(100% - 3.5rem);
width: calc(100% - 4.5rem);
justify-content: center;
align-items: center;
color: var(--error-text-color);
padding: 0.75rem;
text-align: center;
}
.mt-error-icon {
font-size: 2rem;
}
.mt-error-message {
padding: 1rem 0;
}
.mt-error-message hr {
color: var(--line-gray-color);
}
/* Loading spinner */
.mt-body > .loading-spinner {
position: absolute;
width: 3rem;
height: 3rem;
margin: auto;
top: calc(50% - 1.5rem);
right: calc(50% - 1.5rem);
}
.loading-spinner {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 128 128'%3E%3Cg%3E%3CanimateTransform attributeName='transform' type='rotate' from='0 64 64' to='360 64 64' dur='1000ms' repeatCount='indefinite'/%3E%3Cpath d='M64 6.69a57.3 57.3 0 1 1 0 114.61A57.3 57.3 0 0 1 6.69 64' fill='none' stroke='%23404040' stroke-width='12'/%3E%3C/g%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: center center;
background-color: transparent;
background-size: min(2.5rem, calc(100% - 0.5rem));
}
/* Footer (See more link) */
.mt-footer {
margin: 1rem auto 2rem auto;
padding: 0 2rem;
text-align: center;
}
/* Hidden elements */
.visually-hidden {
position: absolute !important;
width: 1px !important;
height: 1px !important;
padding: 0 !important;
margin: -1px !important;
overflow: hidden !important;
clip: rect(0, 0, 0, 0) !important;
white-space: nowrap !important;
border: 0 !important;
}

1
static/css/mastodon-timeline.min.css vendored Normal file

File diff suppressed because one or more lines are too long

BIN
static/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

BIN
static/johnmastodon-sd3.xcf Normal file

Binary file not shown.

View File

@ -0,0 +1,866 @@
/**
* Mastodon embed feed timeline v3.12.0
* More info at:
* https://gitlab.com/idotj/mastodon-embed-feed-timeline
*/
/**
* Timeline settings
* Adjust these parameters to customize your timeline
*/
window.addEventListener("load", () => {
const mastodonTimeline = new MastodonApi({
// Id of the <div> containing the timeline
container_body_id: "mt-body",
// Class name for the loading spinner (also used in CSS file)
spinner_class: "loading-spinner",
// Preferred color theme: 'light', 'dark' or 'auto'. Default: auto
default_theme: "dark",
// Your Mastodon instance
instance_url: "https://infosec.exchange",
// Choose type of toots to show in the timeline: 'local', 'profile', 'hashtag'. Default: local
timeline_type: "hashtag",
// Your user ID number on Mastodon instance. Leave it empty if you didn't choose 'profile' as type of timeline
user_id: "",
// Your user name on Mastodon instance (including the @ symbol at the beginning). Leave it empty if you didn't choose 'profile' as type of timeline
profile_name: "",
// The name of the hashtag (not including the # symbol). Leave it empty if you didn't choose 'hashtag' as type of timeline
hashtag_name: "johnmastodonday",
// Maximum amount of toots to get. Default: 20
toots_limit: "10",
// Hide unlisted toots. Default: don't hide
hide_unlisted: false,
// Hide boosted toots. Default: don't hide
hide_reblog: false,
// Hide replies toots. Default: don't hide
hide_replies: false,
// Hide preview card if toot contains a link, photo or video from a URL. Default: don't hide
hide_preview_link: false,
// Hide custom emojis available on the server. Default: don't hide
hide_emojos: false,
// Converts Markdown symbol ">" at the beginning of a paragraph into a blockquote HTML tag. Ddefault: don't apply
markdown_blockquote: false,
// Hide replies, boosts and favourites toots counter. Default: don't hide
hide_counter_bar: false,
// Limit the text content to a maximum number of lines. Default: 0 (unlimited)
text_max_lines: "4",
// Customize the text of the link pointing to the Mastodon page (appears after the last toot)
link_see_more: "See more posts at Mastodon",
});
});
/**
* Set all variables with customized values or use default ones
* @param {object} params_ User customized values
* Trigger main function to build the timeline
*/
const MastodonApi = function (params_) {
this.CONTAINER_BODY_ID = params_.container_body_id || "mt-body";
this.SPINNER_CLASS = params_.spinner_class || "loading-spinner";
this.DEFAULT_THEME = params_.default_theme || "auto";
this.INSTANCE_URL = params_.instance_url;
this.USER_ID = params_.user_id || "";
this.PROFILE_NAME = this.USER_ID ? params_.profile_name : "";
this.TIMELINE_TYPE = params_.timeline_type || "local";
this.HASHTAG_NAME = params_.hashtag_name || "";
this.TOOTS_LIMIT = params_.toots_limit || "20";
this.HIDE_UNLISTED =
typeof params_.hide_unlisted !== "undefined"
? params_.hide_unlisted
: false;
this.HIDE_REBLOG =
typeof params_.hide_reblog !== "undefined" ? params_.hide_reblog : false;
this.HIDE_REPLIES =
typeof params_.hide_replies !== "undefined" ? params_.hide_replies : false;
this.HIDE_PREVIEW_LINK =
typeof params_.hide_preview_link !== "undefined"
? params_.hide_preview_link
: false;
this.HIDE_EMOJOS =
typeof params_.hide_emojos !== "undefined" ? params_.hide_emojos : false;
this.MARKDOWN_BLOCKQUOTE =
typeof params_.markdown_blockquote !== "undefined"
? params_.markdown_blockquote
: false;
this.HIDE_COUNTER_BAR =
params_.hide_counter_bar !== "undefined" ? params_.hide_counter_bar : false;
this.TEXT_MAX_LINES = params_.text_max_lines || "0";
this.LINK_SEE_MORE = params_.link_see_more;
this.FETCHED_DATA = {};
this.mtBodyContainer = document.getElementById(this.CONTAINER_BODY_ID);
this.buildTimeline();
};
/**
* Trigger functions and construct timeline
*/
MastodonApi.prototype.buildTimeline = async function () {
// Apply color theme
this.setTheme();
// Get server data
await this.getTimelineData();
// Empty the <div> container
this.mtBodyContainer.innerHTML = "";
for (let i in this.FETCHED_DATA.timeline) {
// First filter (Public / Unlisted)
if (
this.FETCHED_DATA.timeline[i].visibility == "public" ||
(!this.HIDE_UNLISTED &&
this.FETCHED_DATA.timeline[i].visibility == "unlisted")
) {
// Second filter (Reblog / Replies)
if (
(this.HIDE_REBLOG && this.FETCHED_DATA.timeline[i].reblog) ||
(this.HIDE_REPLIES && this.FETCHED_DATA.timeline[i].in_reply_to_id)
) {
// Nothing here (Don't append toots)
} else {
// Append toots
this.appendToot(this.FETCHED_DATA.timeline[i], Number(i));
}
}
}
// Check if there are toots in the container (due to filters applied)
if (this.mtBodyContainer.innerHTML === "") {
this.mtBodyContainer.setAttribute("role", "none");
this.mtBodyContainer.innerHTML =
'<div class="mt-error"><span class="mt-error-icon">📭</span><br/><strong>Sorry, no toots to show</strong><br/><div class="mt-error-message">Got ' +
this.FETCHED_DATA.timeline.length +
" toots from the server. <br/>This may be due to an incorrect configuration in the parameters or to filters applied to hide certains type of toots.</div></div>";
} else {
// Insert link after last toot to visit Mastodon page
if (this.LINK_SEE_MORE) {
let linkSeeMorePath = "";
if (this.TIMELINE_TYPE === "profile") {
linkSeeMorePath = this.PROFILE_NAME;
} else if (this.TIMELINE_TYPE === "hashtag") {
linkSeeMorePath = "tags/" + this.HASHTAG_NAME;
} else if (this.TIMELINE_TYPE === "local") {
linkSeeMorePath = "public/local";
}
const linkSeeMore =
'<div class="mt-footer"><a href="' +
this.INSTANCE_URL +
"/" +
this.escapeHtml(linkSeeMorePath) +
'" target="_blank" rel="nofollow noopener noreferrer">' +
this.LINK_SEE_MORE +
"</a></div>";
this.mtBodyContainer.parentNode.insertAdjacentHTML(
"beforeend",
linkSeeMore
);
}
// Control loading spinners
this.manageSpinner();
}
// Toot interactions
this.mtBodyContainer.addEventListener("click", function (e) {
// Check if toot cointainer was clicked
if (
e.target.localName == "article" ||
e.target.offsetParent?.localName == "article" ||
e.target.localName == "img"
) {
openTootURL(e);
}
// Check if Show More/Less button was clicked
if (e.target.localName == "button" && e.target.className == "spoiler-btn") {
toogleSpoiler(e);
}
});
this.mtBodyContainer.addEventListener("keydown", function (e) {
// Check if Enter key was pressed with focus in an article
if (e.key === "Enter" && e.target.localName == "article") {
openTootURL(e);
}
});
/**
* Open toot in a new page avoiding any other natural link
* @param {event} e User interaction trigger
*/
const openTootURL = function (e) {
const urlToot = e.target.closest(".mt-toot").dataset.location;
if (
e.target.localName !== "a" &&
e.target.localName !== "span" &&
e.target.localName !== "button" &&
e.target.localName !== "time" &&
e.target.className !== "mt-toot-preview-noImage" &&
e.target.parentNode.className !== "mt-toot-avatar-image-big" &&
e.target.parentNode.className !== "mt-toot-avatar-image-small" &&
e.target.parentNode.className !== "mt-toot-preview-image" &&
e.target.parentNode.className !== "mt-toot-preview" &&
urlToot
) {
window.open(urlToot, "_blank", "noopener");
}
};
/**
* Spoiler button
* @param {event} e User interaction trigger
*/
const toogleSpoiler = function (e) {
const nextSibling = e.target.nextSibling;
if (nextSibling.localName === "img") {
e.target.parentNode.classList.remove("mt-toot-media-spoiler");
e.target.style.display = "none";
} else if (
nextSibling.classList.contains("spoiler-text-hidden") ||
nextSibling.classList.contains("spoiler-text-visible")
) {
if (e.target.textContent == "Show more") {
nextSibling.classList.remove("spoiler-text-hidden");
nextSibling.classList.add("spoiler-text-visible");
e.target.setAttribute("aria-expanded", "true");
e.target.textContent = "Show less";
} else {
nextSibling.classList.remove("spoiler-text-visible");
nextSibling.classList.add("spoiler-text-hidden");
e.target.setAttribute("aria-expanded", "false");
e.target.textContent = "Show more";
}
}
};
};
/**
* Set the theme style chosen by the user or by the browser/OS
*/
MastodonApi.prototype.setTheme = function () {
/**
* Set the theme value in the <html> tag using the attribute "data-theme"
* @param {string} theme Type of theme to apply: dark or light
*/
const setTheme = function (theme) {
document.documentElement.setAttribute("data-theme", theme);
};
if (this.DEFAULT_THEME === "auto") {
let systemTheme = window.matchMedia("(prefers-color-scheme: dark)");
systemTheme.matches ? setTheme("dark") : setTheme("light");
// Update the theme if user change browser/OS preference
systemTheme.addEventListener("change", (e) => {
e.matches ? setTheme("dark") : setTheme("light");
});
} else {
setTheme(this.DEFAULT_THEME);
}
};
/**
* Requests to the server to get all the data
*/
MastodonApi.prototype.getTimelineData = async function () {
return new Promise((resolve, reject) => {
/**
* Fetch data from server
* @param {string} url address to fetch
* @returns {object} List of objects
*/
async function fetchData(url) {
const response = await fetch(url);
if (!response.ok) {
throw new Error(
"Failed to fetch the following URL: " +
url +
"<hr>" +
"Error status: " +
response.status +
"<hr>" +
"Error message: " +
response.statusText
);
}
const data = await response.json();
return data;
}
// URLs to fetch
let urls = {};
if (this.TIMELINE_TYPE === "profile") {
urls.timeline = `${this.INSTANCE_URL}/api/v1/accounts/${this.USER_ID}/statuses?limit=${this.TOOTS_LIMIT}`;
} else if (this.TIMELINE_TYPE === "hashtag") {
urls.timeline = `${this.INSTANCE_URL}/api/v1/timelines/tag/${this.HASHTAG_NAME}?limit=${this.TOOTS_LIMIT}`;
} else if (this.TIMELINE_TYPE === "local") {
urls.timeline = `${this.INSTANCE_URL}/api/v1/timelines/public?local=true&limit=${this.TOOTS_LIMIT}`;
}
if (!this.HIDE_EMOJOS) {
urls.emojos = this.INSTANCE_URL + "/api/v1/custom_emojis";
}
const urlsPromises = Object.entries(urls).map(([key, url]) => {
return fetchData(url)
.then((data) => ({ [key]: data }))
.catch((error) => {
reject(new Error("Something went wrong fetching data"));
this.mtBodyContainer.innerHTML =
'<div class="mt-error"><span class="mt-error-icon">❌</span><br/><strong>Sorry, request failed:</strong><br/><div class="mt-error-message">' +
error.message +
"</div></div>";
this.mtBodyContainer.setAttribute("role", "none");
return { [key]: [] };
});
});
// Fetch all urls simultaneously
Promise.all(urlsPromises).then((dataObjects) => {
this.FETCHED_DATA = dataObjects.reduce((result, dataItem) => {
return { ...result, ...dataItem };
}, {});
// console.log("Timeline data fetched: ", this.FETCHED_DATA);
resolve();
});
});
};
/**
* Inner function to add each toot in timeline container
* @param {object} c Toot content
* @param {number} i Index of toot
*/
MastodonApi.prototype.appendToot = function (c, i) {
this.mtBodyContainer.insertAdjacentHTML("beforeend", this.assambleToot(c, i));
};
/**
* Build toot structure
* @param {object} c Toot content
* @param {number} i Index of toot
*/
MastodonApi.prototype.assambleToot = function (c, i) {
let avatar,
user,
userName,
url,
date,
formattedDate,
favoritesCount,
reblogCount,
repliesCount;
if (c.reblog) {
// BOOSTED toot
// Toot url
url = c.reblog.url;
// Boosted avatar
avatar =
'<a href="' +
c.reblog.account.url +
'" class="mt-toot-avatar" rel="nofollow noopener noreferrer" target="_blank">' +
'<div class="mt-toot-avatar-boosted">' +
'<div class="mt-toot-avatar-image-big loading-spinner">' +
'<img src="' +
c.reblog.account.avatar +
'" alt="' +
this.escapeHtml(c.reblog.account.username) +
' avatar" loading="lazy" />' +
"</div>" +
'<div class="mt-toot-avatar-image-small">' +
'<img src="' +
c.account.avatar +
'" alt="' +
this.escapeHtml(c.account.username) +
' avatar" loading="lazy" />' +
"</div>" +
"</div>" +
"</a>";
// User name and url
userName = this.showEmojos(
c.reblog.account.display_name
? c.reblog.account.display_name
: c.reblog.account.username,
this.FETCHED_DATA.emojos
);
user =
'<div class="mt-toot-header-user">' +
'<a href="' +
c.reblog.account.url +
'" rel="nofollow noopener noreferrer" target="_blank">' +
userName +
'<span class="visually-hidden"> account</span>' +
"</a>" +
"</div>";
// Date
date = c.reblog.created_at;
// Counter bar
repliesCount = c.reblog.replies_count;
reblogCount = c.reblog.reblogs_count;
favoritesCount = c.reblog.favourites_count;
} else {
// STANDARD toot
// Toot url
url = c.url;
// Avatar
avatar =
'<a href="' +
c.account.url +
'" class="mt-toot-avatar" rel="nofollow noopener noreferrer" target="_blank">' +
'<div class="mt-toot-avatar-standard">' +
'<div class="mt-toot-avatar-image-big loading-spinner">' +
'<img src="' +
c.account.avatar +
'" alt="' +
this.escapeHtml(c.account.username) +
' avatar" loading="lazy" />' +
"</div>" +
"</div>" +
"</a>";
// User name and url
userName = this.showEmojos(
c.account.display_name ? c.account.display_name : c.account.username,
this.FETCHED_DATA.emojos
);
user =
'<div class="mt-toot-header-user">' +
'<a href="' +
c.account.url +
'" rel="nofollow noopener noreferrer" target="_blank">' +
userName +
'<span class="visually-hidden"> account</span>' +
"</a>" +
"</div>";
// Date
date = c.created_at;
// Counter bar
repliesCount = c.replies_count;
reblogCount = c.reblogs_count;
favoritesCount = c.favourites_count;
}
// Date
formattedDate = this.formatDate(date);
const timestamp =
'<div class="mt-toot-header-date">' +
'<a href="' +
url +
'" rel="nofollow noopener noreferrer" target="_blank">' +
'<time datetime="' +
date +
'">' +
formattedDate +
"</time>" +
"</a>" +
"</div>";
// Main text
let text_css = "";
if (this.TEXT_MAX_LINES !== "0") {
text_css = "truncate";
document.documentElement.style.setProperty(
"--text-max-lines",
this.TEXT_MAX_LINES
);
}
let content = "";
if (c.spoiler_text !== "") {
content =
'<div class="mt-toot-text">' +
c.spoiler_text +
' <button type="button" class="spoiler-btn" aria-expanded="false">Show more</button>' +
'<div class="spoiler-text-hidden">' +
this.formatTootText(c.content) +
"</div>" +
"</div>";
} else if (
c.reblog &&
c.reblog.content !== "" &&
c.reblog.spoiler_text !== ""
) {
content =
'<div class="mt-toot-text">' +
c.reblog.spoiler_text +
' <button type="button" class="spoiler-btn" aria-expanded="false">Show more</button>' +
'<div class="spoiler-text-hidden">' +
this.formatTootText(c.reblog.content) +
"</div>" +
"</div>";
} else if (
c.reblog &&
c.reblog.content !== "" &&
c.reblog.spoiler_text === ""
) {
content =
'<div class="mt-toot-text' +
text_css +
'">' +
'<div class="mt-toot-text-wrapper">' +
this.formatTootText(c.reblog.content) +
"</div>" +
"</div>";
} else {
content =
'<div class="mt-toot-text' +
text_css +
'">' +
'<div class="mt-toot-text-wrapper">' +
this.formatTootText(c.content) +
"</div>" +
"</div>";
}
// Media attachments
let media = [];
if (c.media_attachments.length > 0) {
for (let picid in c.media_attachments) {
media.push(this.placeMedias(c.media_attachments[picid], c.sensitive));
}
}
if (c.reblog && c.reblog.media_attachments.length > 0) {
for (let picid in c.reblog.media_attachments) {
media.push(
this.placeMedias(c.reblog.media_attachments[picid], c.reblog.sensitive)
);
}
}
// Preview link
let previewLink = "";
if (!this.HIDE_PREVIEW_LINK && c.card) {
previewLink = this.placePreviewLink(c.card);
}
// Poll
let poll = "";
if (c.poll) {
let pollOption = "";
for (let i in c.poll.options) {
pollOption += "<li>" + c.poll.options[i].title + "</li>";
}
poll =
'<div class="mt-toot-poll ' +
(c.poll.expired ? "mt-toot-poll-expired" : "") +
'">' +
"<ul>" +
pollOption +
"</ul>" +
"</div>";
}
// Counter bar
let counterBar = "";
if (!this.HIDE_COUNTER_BAR) {
const repliesTag =
'<div class="mt-toot-counter-bar-replies">' +
'<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 -960 960 960" aria-hidden="true"><path d="M774.913-185.869V-356q0-56.609-35.891-92.5-35.892-35.891-92.5-35.891H258.045L411.435-331l-56 56.566L105.869-524l249.566-249.566 56 56.566-153.39 153.391h388.477q88.957 0 148.566 59.609 59.608 59.609 59.608 148v170.131h-79.783Z"></path></svg>' +
repliesCount +
"</div>";
const reblogTag =
'<div class="mt-toot-counter-bar-reblog">' +
'<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 -960 960 960" aria-hidden="true"><path d="M276.043-65.304 105.869-236.043l170.174-170.175 52.74 54.175-78.652 78.652h449.304v-160h75.261v235.261H250.131l78.652 78.087-52.74 54.74Zm-90.174-457.348v-235.261h524.565L631.782-836l52.74-54.74L854.696-720 684.522-549.26 631.782-604l78.652-78.652H261.13v160h-75.261Z"></path></svg>' +
reblogCount +
"</div>";
const favoritesTag =
'<div class="mt-toot-counter-bar-favorites">' +
'<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 -960 960 960" aria-hidden="true"><path d="m330.955-216.328 149.066-89 149.066 90.023-40.305-168.391 131.217-114.347-172.956-14.87L480-671.869l-67.043 158.521-172.956 14.305 131.427 113.796-40.473 168.919ZM212.086-50.608l70.652-305.305L45.52-561.305l312.645-26.579L480-876.176l121.835 288.292 312.645 26.579-237.218 205.392 71.217 305.306L480-213.173 212.086-50.607ZM480-433.87Z"></path></svg>' +
favoritesCount +
"</div>";
counterBar =
'<div class="mt-toot-counter-bar">' +
repliesTag +
reblogTag +
favoritesTag +
"</div>";
}
// Add all to main toot container
const toot =
'<article class="mt-toot" aria-posinset="' +
(i + 1) +
'" aria-setsize="' +
this.TOOTS_LIMIT +
'" data-location="' +
url +
'" tabindex="0">' +
'<div class="mt-toot-header">' +
avatar +
user +
timestamp +
"</div>" +
content +
media.join("") +
previewLink +
poll +
counterBar +
"</article>";
return toot;
};
/**
* Handle text changes made to toots
* @param {string} c Text content
* @returns {string} Text content modified
*/
MastodonApi.prototype.formatTootText = function (c) {
let content = c;
// Format hashtags and mentions
content = this.addTarget2hashtagMention(content);
// Convert emojos shortcode into images
if (!this.HIDE_EMOJOS) {
content = this.showEmojos(content, this.FETCHED_DATA.emojos);
}
// Convert markdown styles into HTML
if (this.MARKDOWN_BLOCKQUOTE) {
content = this.replaceHTMLtag(
content,
"<p>&gt;",
"</p>",
"<blockquote><p>",
"</p></blockquote>"
);
}
return content;
};
/**
* Add target="_blank" to all #hashtags and @mentions in the toot
* @param {string} c Text content
* @returns {string} Text content modified
*/
MastodonApi.prototype.addTarget2hashtagMention = function (c) {
let content = c.replaceAll('rel="tag"', 'rel="tag" target="_blank"');
content = content.replaceAll(
'class="u-url mention"',
'class="u-url mention" target="_blank"'
);
return content;
};
/**
* Find all custom emojis shortcode and replace by image
* @param {string} c Text content
* @param {array} e List with all custom emojis
* @returns {string} Text content modified
*/
MastodonApi.prototype.showEmojos = function (c, e) {
if (c.includes(":")) {
for (const emojo of e) {
const regex = new RegExp(`\\:${emojo.shortcode}\\:`, "g");
c = c.replace(
regex,
`<img src="${emojo.url}" class="custom-emoji" alt="Emoji ${emojo.shortcode}" />`
);
}
return c;
} else {
return c;
}
};
/**
* Find all start/end <tags> and replace them by another start/end <tags>
* @param {string} c Text content
* @param {string} initialTagOpen Start HTML tag to replace
* @param {string} initialTagClose End HTML tag to replace
* @param {string} replacedTagOpen New start HTML tag
* @param {string} replacedTagClose New end HTML tag
* @returns {string} Text in HTML format
*/
MastodonApi.prototype.replaceHTMLtag = function (
c,
initialTagOpen,
initialTagClose,
replacedTagOpen,
replacedTagClose
) {
if (c.includes(initialTagOpen)) {
const regex = new RegExp(initialTagOpen + "(.*?)" + initialTagClose, "gi");
return c.replace(regex, replacedTagOpen + "$1" + replacedTagClose);
} else {
return c;
}
};
/**
* Place media
* @param {object} m Media content
* @param {boolean} s Spoiler/Sensitive status
* @returns {string} Media in HTML format
*/
MastodonApi.prototype.placeMedias = function (m, s) {
const spoiler = s || false;
const pic =
'<div class="mt-toot-media img-ratio14_7 ' +
(spoiler ? "mt-toot-media-spoiler " : "") +
this.SPINNER_CLASS +
'">' +
(spoiler ? '<button class="spoiler-btn">Show content</button>' : "") +
'<img src="' +
m.preview_url +
'" alt="' +
(m.description ? this.escapeHtml(m.description) : "") +
'" loading="lazy" />' +
"</div>";
return pic;
};
/**
* Place preview link
* @param {object} c Preview link content
* @returns {string} Preview link in HTML format
*/
MastodonApi.prototype.placePreviewLink = function (c) {
const card =
'<a href="' +
c.url +
'" class="mt-toot-preview" target="_blank" rel="noopener noreferrer">' +
(c.image
? '<div class="mt-toot-preview-image ' +
this.SPINNER_CLASS +
'"><img src="' +
c.image +
'" alt="' +
this.escapeHtml(c.image_description) +
'" loading="lazy" /></div>'
: '<div class="mt-toot-preview-noImage">📄</div>') +
"</div>" +
'<div class="mt-toot-preview-content">' +
(c.provider_name
? '<span class="mt-toot-preview-provider">' +
this.parseHTMLstring(c.provider_name) +
"</span>"
: "") +
'<span class="mt-toot-preview-title">' +
c.title +
"</span>" +
(c.author_name
? '<span class="mt-toot-preview-author">' +
this.parseHTMLstring(c.author_name) +
"</span>"
: "") +
"</div>" +
"</a>";
return card;
};
/**
* Format date
* @param {string} d Date in ISO format (YYYY-MM-DDTHH:mm:ss.sssZ)
* @returns {string} Date formated (MM DD, YYYY)
*/
MastodonApi.prototype.formatDate = function (d) {
const monthNames = [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec",
];
const date = new Date(d);
const displayDate =
monthNames[date.getMonth()] +
" " +
date.getDate() +
", " +
date.getFullYear();
return displayDate;
};
/**
* Parse HTML string
* @param {string} s HTML string
* @returns {string} Plain text
*/
MastodonApi.prototype.parseHTMLstring = function (s) {
const parser = new DOMParser();
const txt = parser.parseFromString(s, "text/html");
return txt.body.textContent;
};
/**
* Escape quotes and other special characters, to make them safe to add
* to HTML content and attributes as plain text
* @param {string} s String
* @returns {string} String
*/
MastodonApi.prototype.escapeHtml = function (s) {
return (s ?? "")
.replaceAll("&", "&amp;")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll('"', "&quot;")
.replaceAll("'", "&#039;");
};
/**
* Add/Remove event listener for loading spinner
*/
MastodonApi.prototype.manageSpinner = function () {
// Remove CSS class to container and listener to images
const spinnerCSS = this.SPINNER_CLASS;
const removeSpinner = function () {
this.parentNode.classList.remove(spinnerCSS);
this.removeEventListener("load", removeSpinner);
this.removeEventListener("error", removeSpinner);
};
// Add listener to images
this.mtBodyContainer
.querySelectorAll(`.${this.SPINNER_CLASS} > img`)
.forEach((e) => {
e.addEventListener("load", removeSpinner);
e.addEventListener("error", removeSpinner);
});
};

1
static/js/mastodon-timeline.min.js vendored Normal file

File diff suppressed because one or more lines are too long

8
static/robots.txt Normal file
View File

@ -0,0 +1,8 @@
User-agent: CCBot
Disallow: /
User-agent: ChatGPT-User
Disallow: /
User-agent: GPTBot
Disallow: /

72
sync.sh Executable file
View File

@ -0,0 +1,72 @@
#!/bin/bash
BUCKET=johnmastodon.me
PROFILE="pacoAdmin"
NORMALAGE=300
# could be something like "foo" if everything is meant to be in /foo
PREFIX=""
AWS="aws --profile ${PROFILE} " # for simple debugging, change this to AWS="echo aws"
hugo
export AWS_DEFAULT_OUTPUT=text
export AWS_PAGER=
export AWS_REGION="us-east-2"
CACHE="--cache-control max-age=\"${NORMALAGE}\",public"
LONGSTUFF="css favicon.ico fonts img js metadata scss webfonts"
LONGAGE=86400
EXCLUDE=""
export AWSLOG="/tmp/sync-log.txt"
for prefix in ${LONGSTUFF}
do
EXCLUDE="${EXCLUDE} --exclude 'public/${prefix}/*'"
done
rm -f ${AWSLOG}
${AWS} s3 sync --only-show-errors \
public s3://${BUCKET} --delete \
${EXCLUDE} \
${CACHE} 2>&1 >> ${AWSLOG}
# reset cache age for long assets
CACHE="--cache-control max-age=\"${LONGAGE}\",public"
for prefix in ${LONGSTUFF}
do
${AWS} s3 sync --only-show-errors \
"public/${prefix}" "s3://${BUCKET}/${prefix}" --delete \
${CACHE} 2>&1 >> ${AWSLOG}
done
# Robots.txt
${AWS} s3 \
cp public/robots.txt s3://${BUCKET}/robots.txt \
--content-type text/plain \
${CACHE} 2>&1 >> ${AWSLOG}
# back to normal for special files
CACHE="--cache-control max-age=\"${NORMALAGE}\",public"
# Handle some one-offs
${AWS} s3 \
cp public/sitemap.xml s3://${BUCKET}/sitemap.xml \
--content-type text/xml \
${CACHE} 2>&1 >> ${AWSLOG}
# back to normal for index files
CACHE="--cache-control max-age=\"${NORMALAGE}\",public"
echo "Writing index files to directory markers"
time (find public/* -name index.html | while read -r line; do
# ${AWS} s3 cp $line "s3://$BUCKET/${BASH_REMATCH[1]}"
# get rid of "public/"
OBJKEY=${line#public/*}
# get rid of "/index.html"
OBJKEY=${OBJKEY%/index.html}
echo "Uploading from $line to s3://${BUCKET}/${OBJKEY}/"
${AWS} s3api put-object --bucket "$BUCKET" \
--body ${line} --key "${OBJKEY}" --content-type text/html ${CACHE} 2>&1 >> ${AWSLOG}
${AWS} s3api put-object --bucket "$BUCKET" \
--body ${line} --key "${OBJKEY}/" --content-type text/html ${CACHE} 2>&1 >> ${AWSLOG}
done)