Adding customisations
parent
9fb3e926c6
commit
82313462fe
Binary file not shown.
|
@ -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');
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
File diff suppressed because one or more lines are too long
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 |
Binary file not shown.
|
@ -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>>",
|
||||
"</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("&", "&")
|
||||
.replaceAll("<", "<")
|
||||
.replaceAll(">", ">")
|
||||
.replaceAll('"', """)
|
||||
.replaceAll("'", "'");
|
||||
};
|
||||
|
||||
/**
|
||||
* 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);
|
||||
});
|
||||
};
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,8 @@
|
|||
User-agent: CCBot
|
||||
Disallow: /
|
||||
|
||||
User-agent: ChatGPT-User
|
||||
Disallow: /
|
||||
|
||||
User-agent: GPTBot
|
||||
Disallow: /
|
|
@ -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)
|
Loading…
Reference in New Issue