Merge branch 'feature/lightbox-carousel' into 'master'

Feature/lightbox-carousel

See merge request idotj/mastodon-embed-timeline!32
This commit is contained in:
i.j 2024-03-09 11:31:09 +00:00
commit 164c45bf9e
11 changed files with 787 additions and 205 deletions

View File

@ -1,3 +1,9 @@
v4.3.5 - 09/03/2024
- Add a carousel/lightbox for pictures and videos in a post
- Improve data set for media items
- Enable loop for videos by default
- Small UI improvements
v4.3.3 - 01/03/2024
- Fix click conflict on user name
- Render emojos in user name
@ -58,7 +64,7 @@ v3.13.1 - 12/01/2024
v3.12.0 - 11/12/2023
- Fix link preview event on click
v3.11.0 - 4/11/2023
v3.11.0 - 04/11/2023
- Update icons
- Improve loader spinner
@ -103,7 +109,7 @@ v3.8.1 - 14/08/2023
- Improve JS comments
v3.8.0 - 04/08/2023
- Add spoiler/sensitive button for reblog content
- Add sensitive/spoiler button for reblog content
v3.7.2 - 25/07/2023
- Use window.onload to take async attribute into account

View File

@ -2,7 +2,7 @@
![Mastodon timeline widget screenshot](screenshot-light-dark.jpg "Mastodon timeline widget screenshot")
Embed a mastodon timeline in your page, only with a CSS and JS file.
Embed a Mastodon timeline in your page, only with a CSS and JS file.
Demo running:
<https://codepen.io/ipuntoj/pen/MWppNGL>
@ -65,11 +65,11 @@ This option allows you to start without the need to upload any files on your ser
Copy the following CSS and JS links to include them in your project:
```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@idotj/mastodon-embed-timeline@4.3.3/dist/mastodon-timeline.min.css" integrity="sha256-n6277x0TxwslF9uskcdwCPorYZnoSB9Wbv1O1w7Ahds=" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@idotj/mastodon-embed-timeline@4.3.5/dist/mastodon-timeline.min.css" crossorigin="anonymous">
```
```html
<script src="https://cdn.jsdelivr.net/npm/@idotj/mastodon-embed-timeline@4.3.3/dist/mastodon-timeline.umd.js" integrity="sha256-H+NYFuLL1tG4+iQmE5LRh5zBg1bToiCK/k4uJi4n3ks=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/@idotj/mastodon-embed-timeline@4.3.5/dist/mastodon-timeline.umd.js" crossorigin="anonymous"></script>
```
### Package manager
@ -108,7 +108,7 @@ To get your timeline up add the following HTML structure in your page:
</div>
```
Then after that you can initialize the script by running:
Now you can then initialize the script running:
```js
const myTimeline = new MastodonTimeline.Init();
@ -246,7 +246,7 @@ Here you have all the options available to quickly setup and customize your time
// Default: don't hide
hidePinnedPosts: false,
// Hide user account under the user name
// Hide the user account under the user name
// Default: don't hide
hideUserAccount: false,
@ -270,15 +270,24 @@ Here you have all the options available to quickly setup and customize your time
// Default: don't apply
markdownBlockquote: false,
// Show a carousel/lightbox when the user clicks on a picture in a post
// Default: not disabled
disableCarousel: false,
// Customize the text of the buttons used for the carousel/lightbox
carouselCloseTxt: "Close carousel",
carouselPrevTxt: "Previous media item",
carouselNextTxt: "Next media item",
// Limit the text content to a maximum number of lines
// Default: 0 (unlimited)
txtMaxLines: "0",
// Customize the text of the button used for showing/hiding sensitive/spolier text
// Customize the text of the button used for showing/hiding sensitive/spoiler text
btnShowMore: "SHOW MORE",
btnShowLess: "SHOW LESS",
// Customize the text of the button used for showing sensitive/spolier media content
// Customize the text of the button used for showing sensitive/spoiler media content
btnShowContent: "SHOW CONTENT",
// Customize the text of the button pointing to the Mastodon page placed at the end of the timeline

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -60,24 +60,42 @@
margin: 2rem 0;
}
/* Customized CSS styles */
/* Example of customized CSS styles */
.mt-container {
font-family: monospace;
background-color: transparent;
border: 1px solid white;
}
.mt-post {
border-bottom: none;
box-shadow: 2px 2px 6px 2px rgba(0, 0, 0, 0.25);
margin-bottom: 1rem;
}
.mt-post-avatar-image-big img {
border-radius: 0;
}
.mt-container a,
.mt-container a:active,
.mt-container a:link {
color: darkgreen;
}
.mt-post-header {
margin-bottom: 0;
}
.mt-post-avatar-image-big img {
border-radius: 0;
}
.mt-post-header-user {
margin-right: auto;
}
.mt-post-header-date {
position: absolute;
bottom: 1rem;
left: 3.5rem;
}
.mt-post {
border-bottom: none;
box-shadow: 2px 2px 6px 2px rgba(0, 0, 0, 0.25);
margin-bottom: 1rem;
padding-bottom: 2rem;
}
.mt-post-txt,
.mt-post-media-wrapper {
width: calc(100% - 3rem);
margin-left: auto;
}
</style>
</head>
@ -105,8 +123,7 @@
<p>
At JS level, it defaults to the light theme and the date is displayed
in US format using digits only. In order to achieve a minimalist
style, the following options have been changed at its
initialization:
style, the following options have been changed at its initialization:
</p>
<pre>
<code>

189
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "@idotj/mastodon-embed-timeline",
"version": "4.3.3",
"version": "4.3.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@idotj/mastodon-embed-timeline",
"version": "4.3.3",
"version": "4.3.5",
"license": "GNU",
"devDependencies": {
"@rollup/plugin-terser": "^0.4.4",
@ -15,14 +15,14 @@
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
"integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
"integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
"dev": true,
"dependencies": {
"@jridgewell/set-array": "^1.0.1",
"@jridgewell/set-array": "^1.2.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
"@jridgewell/trace-mapping": "^0.3.9"
"@jridgewell/trace-mapping": "^0.3.24"
},
"engines": {
"node": ">=6.0.0"
@ -38,9 +38,9 @@
}
},
"node_modules/@jridgewell/set-array": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
"integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
"dev": true,
"engines": {
"node": ">=6.0.0"
@ -63,9 +63,9 @@
"dev": true
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.22",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz",
"integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==",
"version": "0.3.24",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.24.tgz",
"integrity": "sha512-+VaWXDa6+l6MhflBvVXjIEAzb59nQ2JUK3bwRp2zRpPtU+8TFRy9Gg/5oIcNlkEL5PGlBFGfemUVvIgLnTzq7Q==",
"dev": true,
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
@ -94,6 +94,110 @@
}
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.12.0.tgz",
"integrity": "sha512-+ac02NL/2TCKRrJu2wffk1kZ+RyqxVUlbjSagNgPm94frxtr+XDL12E5Ll1enWskLrtrZ2r8L3wED1orIibV/w==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"android"
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.12.0.tgz",
"integrity": "sha512-OBqcX2BMe6nvjQ0Nyp7cC90cnumt8PXmO7Dp3gfAju/6YwG0Tj74z1vKrfRz7qAv23nBcYM8BCbhrsWqO7PzQQ==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"android"
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.12.0.tgz",
"integrity": "sha512-X64tZd8dRE/QTrBIEs63kaOBG0b5GVEd3ccoLtyf6IdXtHdh8h+I56C2yC3PtC9Ucnv0CpNFJLqKFVgCYe0lOQ==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.12.0.tgz",
"integrity": "sha512-cc71KUZoVbUJmGP2cOuiZ9HSOP14AzBAThn3OU+9LcA1+IUqswJyR1cAJj3Mg55HbjZP6OLAIscbQsQLrpgTOg==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.12.0.tgz",
"integrity": "sha512-a6w/Y3hyyO6GlpKL2xJ4IOh/7d+APaqLYdMf86xnczU3nurFTaVN9s9jOXQg97BE4nYm/7Ga51rjec5nfRdrvA==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.12.0.tgz",
"integrity": "sha512-0fZBq27b+D7Ar5CQMofVN8sggOVhEtzFUwOwPppQt0k+VR+7UHMZZY4y+64WJ06XOhBTKXtQB/Sv0NwQMXyNAA==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.12.0.tgz",
"integrity": "sha512-eTvzUS3hhhlgeAv6bfigekzWZjaEX9xP9HhxB0Dvrdbkk5w/b+1Sxct2ZuDxNJKzsRStSq1EaEkVSEe7A7ipgQ==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.12.0.tgz",
"integrity": "sha512-ix+qAB9qmrCRiaO71VFfY8rkiAZJL8zQRXveS27HS+pKdjwUfEhqo2+YF2oI+H/22Xsiski+qqwIBxVewLK7sw==",
"cpu": [
"riscv64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.12.0.tgz",
@ -120,6 +224,45 @@
"linux"
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.12.0.tgz",
"integrity": "sha512-JPDxovheWNp6d7AHCgsUlkuCKvtu3RB55iNEkaQcf0ttsDU/JZF+iQnYcQJSk/7PtT4mjjVG8N1kpwnI9SLYaw==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.12.0.tgz",
"integrity": "sha512-fjtuvMWRGJn1oZacG8IPnzIV6GF2/XG+h71FKn76OYFqySXInJtseAqdprVTDTyqPxQOG9Exak5/E9Z3+EJ8ZA==",
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.12.0.tgz",
"integrity": "sha512-ZYmr5mS2wd4Dew/JjT0Fqi2NPB/ZhZ2VvPp7SmvPZb4Y1CG/LRcS6tcRo2cYU7zLK5A7cdbhWnnWmUjoI4qapg==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"win32"
]
},
"node_modules/@types/estree": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
@ -281,6 +424,20 @@
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
"dev": true
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
@ -530,9 +687,9 @@
}
},
"node_modules/terser": {
"version": "5.27.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.27.1.tgz",
"integrity": "sha512-29wAr6UU/oQpnTw5HoadwjUZnFQXGdOfj0LjZ4sVxzqwHh/QVkvr7m8y9WoR4iN3FRitVduTc6KdjcW38Npsug==",
"version": "5.28.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.28.1.tgz",
"integrity": "sha512-wM+bZp54v/E9eRRGXb5ZFDvinrJIOaTapx3WUokyVGZu5ucVCK55zEgGd5Dl2fSr3jUo5sDiERErUWLY6QPFyA==",
"dev": true,
"dependencies": {
"@jridgewell/source-map": "^0.3.3",

View File

@ -1,6 +1,6 @@
{
"name": "@idotj/mastodon-embed-timeline",
"version": "4.3.3",
"version": "4.3.5",
"description": "Displays Mastodon timeline with posts embed in your website. Very easy to setup, no dependencies, no trackers, cross-browser, WCAG compliant and fully responsive.",
"license": "GNU",
"author": {

View File

@ -1,10 +1,12 @@
/* Mastodon embed timeline v4.3.3 */
/* Mastodon embed timeline v4.3.5 */
/* More info at: */
/* https://gitlab.com/idotj/mastodon-embed-timeline */
/* Variables */
.mt-container,
.mt-container[data-theme="light"] {
.mt-dialog,
.mt-container[data-theme="light"],
.mt-dialog[data-theme="light"] {
--mt-txt-max-lines: none;
--mt-color-bg: #fff;
--mt-color-bg-hover: #d9e1e8;
@ -16,8 +18,10 @@
--mt-color-btn-bg: #6364ff;
--mt-color-btn-bg-hover: #563acc;
--mt-color-btn-txt: #fff;
--mt-color-backdrop: #00000090;
}
.mt-container[data-theme="dark"] {
.mt-container[data-theme="dark"],
.mt-dialog[data-theme="dark"] {
--mt-color-bg: #282c37;
--mt-color-bg-hover: #313543;
--mt-color-line-gray: #393f4f;
@ -28,11 +32,13 @@
}
/* Reset CSS */
.mt-container button {
.mt-container button,
.mt-dialog button {
font: inherit;
}
.mt-container a,
.mt-container button {
.mt-container button,
.mt-dialog button {
cursor: pointer;
}
@ -254,18 +260,30 @@
}
/* Medias */
.mt-post-media-wrapper {
display: flex;
flex-direction: column;
gap: 1rem;
margin-bottom: 1rem;
}
.mt-post-media {
position: relative;
overflow: hidden;
margin-bottom: 1rem;
}
.mt-post-media-spoiler > img,
.mt-post-media-spoiler > audio,
.mt-post-media-spoiler > video,
.mt-post-media-spoiler > .mt-post-media-play-icon {
.mt-post-media-spoiler > .mt-btn-play {
filter: blur(2rem);
pointer-events: none;
}
.mt-post-media,
.mt-post-media-spoiler > img,
.mt-post-media-spoiler > audio,
.mt-post-media > img,
.mt-post-media > video {
border-radius: 0.5rem;
}
.mt-post-media > audio {
width: 100%;
position: relative;
@ -282,28 +300,93 @@
text-align: center;
color: var(--mt-color-content-txt);
}
.mt-post-media.mt-loading-spinner .mt-post-media-play-icon {
display: none;
/* Dialog - modal */
body:has(dialog.mt-dialog[open]) {
overflow: hidden;
}
.mt-post-media-play-icon {
.mt-dialog {
display: flex;
position: absolute;
width: 3rem;
height: 3rem;
top: calc(50% - 1.5rem);
left: calc(50% - 1.5rem);
justify-content: center;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
border: none;
color: var(--mt-color-content-txt);
background-color: transparent;
padding: 0;
margin: 1rem;
overflow: hidden;
}
.mt-dialog::backdrop {
background-color: var(--mt-color-backdrop);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
}
/* Carousel/lightbox */
.mt-carousel-header {
position: absolute;
top: 0;
right: 0;
z-index: 2;
}
.mt-carousel-body {
width: 100%;
height: 100%;
}
.mt-carousel-scroll {
display: flex;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
list-style: none;
overflow-x: auto;
overflow-y: hidden;
scroll-snap-type: x mandatory;
scroll-behavior: smooth;
scrollbar-width: none;
}
.mt-carousel-scroll::-webkit-scrollbar {
display: none;
-webkit-appearance: none;
}
.mt-carousel-item {
scroll-snap-align: center;
width: 100%;
height: 100%;
}
.mt-carousel-media-wrapper {
width: calc(100vw - 2.5rem);
height: 100%;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
}
.mt-carousel-media {
width: 100%;
height: 100%;
object-fit: contain;
padding: 2rem;
}
.mt-carousel-prev,
.mt-carousel-next {
position: absolute;
background-color: transparent;
border: none;
cursor: pointer;
padding: 0.5rem;
z-index: 2;
}
.mt-post-media-play-icon > svg {
width: 2.5rem;
height: 2.5rem;
fill: var(--mt-color-bg);
stroke: var(--mt-color-content-txt);
stroke-width: 1px;
.mt-carousel-prev {
left: 0;
padding-left: 0;
}
.mt-carousel-next {
right: 0;
padding-right: 0;
}
/* Preview link */
@ -372,7 +455,31 @@
}
/* Buttons */
.mt-container .mt-btn-dark {
.mt-btn-play {
display: flex;
position: absolute;
width: 3rem;
height: 3rem;
top: calc(50% - 1.5rem);
left: calc(50% - 1.5rem);
justify-content: center;
align-items: center;
background-color: transparent;
border: none;
cursor: pointer;
}
.mt-btn-play > svg {
width: 2.5rem;
height: 2.5rem;
fill: var(--mt-color-bg);
stroke: var(--mt-color-content-txt);
stroke-width: 1px;
}
.mt-post-media.mt-loading-spinner .mt-btn-play {
display: none;
}
.mt-container .mt-btn-dark,
.mt-dialog .mt-btn-dark {
display: flex;
border-radius: 0.25rem;
background-color: var(--mt-color-line-gray);
@ -381,11 +488,15 @@
font-weight: 600;
font-size: 0.75rem;
text-align: center;
padding: 0 0.5rem;
padding: 0.25rem 0.5rem;
line-height: 1.25rem;
vertical-align: top;
}
.mt-dialog .mt-btn-dark {
margin-left: auto;
}
.mt-dialog .mt-btn-violet,
.mt-dialog a.mt-btn-violet,
.mt-container .mt-btn-violet,
.mt-container a.mt-btn-violet {
display: flex;
@ -400,6 +511,8 @@
background-color: var(--mt-color-btn-bg);
color: var(--mt-color-btn-txt);
}
.mt-dialog .mt-btn-violet:hover,
.mt-dialog a.mt-btn-violet:hover,
.mt-container .mt-btn-violet:hover,
.mt-container a.mt-btn-violet:hover {
background-color: var(--mt-color-btn-bg-hover);
@ -407,6 +520,7 @@
}
.mt-post-txt .mt-btn-spoiler {
display: inline-block;
vertical-align: middle;
}
.mt-post-media.mt-loading-spinner > .mt-btn-spoiler {
display: none;
@ -436,6 +550,7 @@
}
.mt-error-icon {
font-size: 2rem;
margin-bottom: 1rem;
}
.mt-error-message {
width: 100%;

View File

@ -1,7 +1,7 @@
/**
* Mastodon embed timeline
* @author idotj
* @version 4.3.3
* @version 4.3.5
* @url https://gitlab.com/idotj/mastodon-embed-timeline
* @license GNU AGPLv3
*/
@ -36,6 +36,10 @@ export class Init {
hidePreviewLink: false,
hideCounterBar: false,
markdownBlockquote: false,
disableCarousel: false,
carouselCloseTxt: "Close carousel",
carouselPrevTxt: "Previous media item",
carouselNextTxt: "Next media item",
txtMaxLines: "0",
btnShowMore: "SHOW MORE",
btnShowLess: "SHOW LESS",
@ -172,42 +176,17 @@ export class Init {
* Requests to the server to collect all the data
* @returns {object} Data container
*/
#fetchTimelineData() {
#getTimelineData() {
return new Promise((resolve, reject) => {
/**
* Fetch data from server
* @param {string} url address to fetch
* @returns {array} List of objects
*/
async function fetchData(url) {
const response = await fetch(url);
if (!response.ok) {
throw new Error(
"Failed to fetch the following Url: <br>" +
url +
"<hr>" +
"Error status: " +
response.status +
"<hr>" +
"Error message: " +
response.statusText
);
}
const data = await response.json();
return data;
}
// Urls to fetch
const instanceApiUrl = `${this.mtSettings.instanceUrl}/api/v1/`;
let urls = {};
if (this.mtSettings.instanceUrl) {
if (this.mtSettings.timelineType === "profile") {
if (this.mtSettings.userId) {
urls.timeline = `${this.mtSettings.instanceUrl}/api/v1/accounts/${this.mtSettings.userId}/statuses?limit=${this.mtSettings.maxNbPostFetch}`;
urls.timeline = `${instanceApiUrl}accounts/${this.mtSettings.userId}/statuses?limit=${this.mtSettings.maxNbPostFetch}`;
if (!this.mtSettings.hidePinnedPosts) {
urls.pinned = `${this.mtSettings.instanceUrl}/api/v1/accounts/${this.mtSettings.userId}/statuses?pinned=true`;
urls.pinned = `${instanceApiUrl}accounts/${this.mtSettings.userId}/statuses?pinned=true`;
}
} else {
this.#showError(
@ -217,7 +196,7 @@ export class Init {
}
} else if (this.mtSettings.timelineType === "hashtag") {
if (this.mtSettings.hashtagName) {
urls.timeline = `${this.mtSettings.instanceUrl}/api/v1/timelines/tag/${this.mtSettings.hashtagName}?limit=${this.mtSettings.maxNbPostFetch}`;
urls.timeline = `${instanceApiUrl}timelines/tag/${this.mtSettings.hashtagName}?limit=${this.mtSettings.maxNbPostFetch}`;
} else {
this.#showError(
"Please check your <strong>hashtagName</strong> value",
@ -225,7 +204,7 @@ export class Init {
);
}
} else if (this.mtSettings.timelineType === "local") {
urls.timeline = `${this.mtSettings.instanceUrl}/api/v1/timelines/public?local=true&limit=${this.mtSettings.maxNbPostFetch}`;
urls.timeline = `${instanceApiUrl}timelines/public?local=true&limit=${this.mtSettings.maxNbPostFetch}`;
} else {
this.#showError(
"Please check your <strong>timelineType</strong> value",
@ -239,15 +218,15 @@ export class Init {
);
}
if (!this.mtSettings.hideEmojos) {
urls.emojos = this.mtSettings.instanceUrl + "/api/v1/custom_emojis";
urls.emojos = `${instanceApiUrl}custom_emojis`;
}
const urlsPromises = Object.entries(urls).map(([key, url]) => {
return fetchData(url)
return this.#fetchData(url)
.then((data) => ({ [key]: data }))
.catch((error) => {
reject(
new Error("Something went wrong fetching data from: " + url)
new Error(`Something went wrong fetching data from: ${url}`)
);
this.#showError(error.message);
return { [key]: [] };
@ -266,12 +245,30 @@ export class Init {
});
}
/**
* Fetch data from server
* @param {string} url address to fetch
* @returns {array} List of objects
*/
async #fetchData(url) {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`
Failed to fetch the following Url:<br/>${url}<hr>Error status: ${response.status}<hr>Error message: ${response.statusText}
`);
}
const data = await response.json();
return data;
}
/**
* Filter all fetched posts and append them on the timeline
* @param {string} t Type of build (new or reload)
*/
async #buildTimeline(t) {
await this.#fetchTimelineData();
await this.#getTimelineData();
// Merge pinned posts with timeline posts
let posts;
@ -320,10 +317,9 @@ export class Init {
// If there are no posts to display, show an error message
if (this.mtBodyNode.innerHTML === "") {
const errorMessage =
"No posts to show <hr/>" +
(posts?.length || 0) +
" posts have been fetched from the server <hr/>This may be due to an incorrect configuration in the parameters or to filters applied (to hide certains type of posts)";
const errorMessage = `No posts to show<hr/>${
posts?.length || 0
} posts have been fetched from the server<hr/>This may be due to an incorrect configuration with the parameters or with the filters applied (to hide certains type of posts)`;
this.#showError(errorMessage, "📭");
} else {
if (t === "newTimeline") {
@ -379,14 +375,14 @@ export class Init {
'<img src="' +
c.reblog.account.avatar +
'" alt="' +
this.#escapeHtml(c.reblog.account.username) +
this.#escapeHTML(c.reblog.account.username) +
' avatar" loading="lazy" />' +
"</div>" +
'<div class="mt-post-avatar-image-small">' +
'<img src="' +
c.account.avatar +
'" alt="' +
this.#escapeHtml(c.account.username) +
this.#escapeHTML(c.account.username) +
' avatar" loading="lazy" />' +
"</div>" +
"</div>" +
@ -399,7 +395,9 @@ export class Init {
c.reblog.account.emojis
);
} else {
userName = c.reblog.account.display_name ? c.reblog.account.display_name : c.reblog.account.username;
userName = c.reblog.account.display_name
? c.reblog.account.display_name
: c.reblog.account.username;
}
if (!this.mtSettings.hideUserAccount) {
@ -446,7 +444,7 @@ export class Init {
'<img src="' +
c.account.avatar +
'" alt="' +
this.#escapeHtml(c.account.username) +
this.#escapeHTML(c.account.username) +
' avatar" loading="lazy" />' +
"</div>" +
"</div>" +
@ -459,7 +457,9 @@ export class Init {
c.account.emojis
);
} else {
userName = c.account.display_name ? c.account.display_name : c.account.username;
userName = c.account.display_name
? c.account.display_name
: c.account.username;
}
if (!this.mtSettings.hideUserAccount) {
@ -495,22 +495,20 @@ export class Init {
// Date
formattedDate = this.#formatDate(date);
const timestamp =
'<div class="mt-post-header-date">' +
(c.pinned
? '<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24" class="mt-post-pinned" aria-hidden="true"><path d="m640-480 80 80v80H520v240l-40 40-40-40v-240H240v-80l80-80v-280h-40v-80h400v80h-40v280Zm-286 80h252l-46-46v-314H400v314l-46 46Zm126 0Z"></path></svg>'
: "") +
'<a href="' +
url +
'" rel="nofollow noopener noreferrer" target="_blank">' +
'<time datetime="' +
date +
'">' +
formattedDate +
"</time>" +
(c.edited_at ? " *" : "") +
"</a>" +
"</div>";
const timestamp = `
<div class="mt-post-header-date">
${
c.pinned
? '<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24" class="mt-post-pinned" aria-hidden="true"><path d="m640-480 80 80v80H520v240l-40 40-40-40v-240H240v-80l80-80v-280h-40v-80h400v80h-40v280Zm-286 80h252l-46-46v-314H400v314l-46 46Zm126 0Z"></path></svg>'
: ""
}
<a href="${url}" rel="nofollow noopener noreferrer" target="_blank">
<time datetime="${date}">
${formattedDate}
</time>
${c.edited_at ? " *" : ""}
</a>
</div>`;
// Main text
let txtCss = "";
@ -587,6 +585,7 @@ export class Init {
);
}
}
media = `<div class="mt-post-media-wrapper">${media.join("")}</div>`;
// Preview link
let previewLink = "";
@ -655,7 +654,7 @@ export class Init {
timestamp +
"</div>" +
content +
media.join("") +
media +
previewLink +
poll +
counterBar +
@ -743,7 +742,7 @@ export class Init {
* @param {string} s String
* @returns {string} String
*/
#escapeHtml(s) {
#escapeHTML(s) {
return (s ?? "")
.replaceAll("&", "&amp;")
.replaceAll("<", "&lt;")
@ -793,7 +792,7 @@ export class Init {
/**
* Create media element
* @param {object} m Media content
* @param {boolean} s Spoiler/Sensitive status
* @param {boolean} s Sensitive/spoiler status
* @returns {string} Media in HTML format
*/
#createMedia(m, s) {
@ -806,6 +805,16 @@ export class Init {
'<div class="mt-post-media ' +
(spoiler ? "mt-post-media-spoiler " : "") +
this.mtSettings.spinnerClass +
'" data-media-type="' +
m.type +
'" data-media-url-hd="' +
m.url +
'" data-media-alt-txt="' +
(m.description ? this.#escapeHTML(m.description) : "") +
'" data-media-width-hd="' +
m.meta.original.width +
'" data-media-height-hd="' +
m.meta.original.height +
'" style="padding-top: calc(100%/' +
m.meta.small.aspect +
')">' +
@ -817,7 +826,7 @@ export class Init {
'<img src="' +
m.preview_url +
'" alt="' +
(m.description ? this.#escapeHtml(m.description) : "") +
(m.description ? this.#escapeHTML(m.description) : "") +
'" loading="lazy" />' +
"</div>";
}
@ -828,6 +837,16 @@ export class Init {
'<div class="mt-post-media ' +
(spoiler ? "mt-post-media-spoiler " : "") +
this.mtSettings.spinnerClass +
'" data-media-type="' +
m.type +
'" data-media-url-hd="' +
m.preview_url +
'" data-media-alt-txt="' +
(m.description ? this.#escapeHTML(m.description) : "") +
'" data-media-width-hd="' +
m.meta.small.width +
'" data-media-height-hd="' +
m.meta.small.height +
'" style="padding-top: calc(100%/' +
m.meta.small.aspect +
')">' +
@ -842,13 +861,15 @@ export class Init {
'<img src="' +
m.preview_url +
'" alt="' +
(m.description ? this.#escapeHtml(m.description) : "") +
(m.description ? this.#escapeHTML(m.description) : "") +
'" loading="lazy" />' +
"</div>";
} else {
media =
'<div class="mt-post-media ' +
(spoiler ? "mt-post-media-spoiler " : "") +
'" data-media-type="' +
m.type +
'">' +
(spoiler
? '<button class="mt-btn-dark mt-btn-spoiler">' +
@ -868,8 +889,16 @@ export class Init {
'<div class="mt-post-media ' +
(spoiler ? "mt-post-media-spoiler " : "") +
this.mtSettings.spinnerClass +
'" data-video-url="' +
'" data-media-type="' +
m.type +
'" data-media-url-hd="' +
m.url +
'" data-media-alt-txt="' +
(m.description ? this.#escapeHTML(m.description) : "") +
'" data-media-width-hd="' +
m.meta.original.width +
'" data-media-height-hd="' +
m.meta.original.height +
'" style="padding-top: calc(100%/' +
m.meta.small.aspect +
')">' +
@ -881,14 +910,24 @@ export class Init {
'<img src="' +
m.preview_url +
'" alt="' +
(m.description ? this.#escapeHtml(m.description) : "") +
(m.description ? this.#escapeHTML(m.description) : "") +
'" loading="lazy" />' +
'<button class="mt-post-media-play-icon" title="Load video"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 14"><path d="M9.5 7l-9 6.3V.7z"/></svg></button>' +
'<button class="mt-btn-play" title="Load video"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 14"><path d="M9.5 7l-9 6.3V.7z"/></svg></button>' +
"</div>";
} else {
media =
'<div class="mt-post-media ' +
(spoiler ? "mt-post-media-spoiler " : "") +
'" data-media-type="' +
m.type +
'" data-media-url-hd="' +
m.url +
'" data-media-alt-txt="' +
(m.description ? this.#escapeHTML(m.description) : "") +
'" data-media-width-hd="' +
m.meta.original.width +
'" data-media-width-hd="' +
m.meta.original.height +
'" style="padding-top: calc(100%/' +
m.meta.small.aspect +
')">' +
@ -899,7 +938,7 @@ export class Init {
: "") +
'<video controls src="' +
m.url +
'"></video>' +
'" loop></video>' +
"</div>";
}
}
@ -907,16 +946,234 @@ export class Init {
return media;
}
/**
* Open a dialog/modal with the styles of Mastodon timeline
* @param {string} i Dialog Id name
* @param {string} c Dialog HTML content
*/
#openDialog(i, c) {
let dialog = document.createElement("dialog");
dialog.id = i;
dialog.classList.add("mt-dialog");
dialog.dataset.theme = this.mtContainerNode.getAttribute("data-theme");
dialog.innerHTML = c;
document.body.prepend(dialog);
dialog.showModal();
dialog.addEventListener("close", () => {
document.body.removeChild(dialog);
});
}
/**
* Build a carousel/lightbox with the media content in the post clicked
* @param {event} e User interaction trigger
*/
#buildCarousel(e) {
// List all medias in the post and remove sensitive/spoiler medias
const mediaSiblings = Array.from(
e.target.parentNode.parentNode.children
).filter((element) => !element.classList.contains("mt-post-media-spoiler"));
const mediaClickedIndex = mediaSiblings.indexOf(e.target.parentNode) + 1;
// Build media element and wrapper
let mediaItems = [];
mediaSiblings.forEach((sibling, i) => {
let mediaElement = "";
if (
sibling.getAttribute("data-media-type") === "gifv" ||
sibling.getAttribute("data-media-type") === "video"
) {
mediaElement = `
<video controls src="${sibling.getAttribute(
"data-media-url-hd"
)}" width="${sibling.getAttribute(
"data-media-width-hd"
)}" height="${sibling.getAttribute(
"data-media-height-hd"
)}" class="mt-carousel-media" style="max-width:${sibling.getAttribute(
"data-media-width-hd"
)}px; max-height:${sibling.getAttribute(
"data-media-height-hd"
)}px" loop>
</video>
`;
} else {
mediaElement = `
<img src="${sibling.getAttribute(
"data-media-url-hd"
)}" width="${sibling.getAttribute(
"data-media-width-hd"
)}" height="${sibling.getAttribute(
"data-media-height-hd"
)}" class="mt-carousel-media mt-loading-spinner" alt="${sibling.getAttribute(
"data-media-alt-txt"
)}" style="max-width:${sibling.getAttribute(
"data-media-width-hd"
)}px; max-height:${sibling.getAttribute(
"data-media-height-hd"
)}px" dragabble="false" />
`;
}
const mediaWrapper = `
<li class="mt-carousel-item">
<div id="mt-carousel-${
i + 1
}" class="mt-carousel-media-wrapper" data-media-type="${sibling.getAttribute(
"data-media-type"
)}">
${mediaElement}
</div>
</li>
`;
mediaItems.push(mediaWrapper);
});
// Build carousel
const carouselHTML = `
<div class="mt-carousel-header">
<form method="dialog">
<button id="mt-carousel-close" class="mt-btn-dark" title="${
this.mtSettings.carouselCloseTxt
}">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
<path fill-rule="evenodd" d="M5.293 5.293a1 1 0 0 1 1.414 0L12 10.586l5.293-5.293a1 1 0 0 1 1.414 1.414L13.414 12l5.293 5.293a1 1 0 0 1-1.414 1.414L12 13.414l-5.293 5.293a1 1 0 0 1-1.414-1.414L10.586 12 5.293 6.707a1 1 0 0 1 0-1.414z" fill="var(--mt-color-content-txt)"/>
</svg>
</button>
</form>
</div>
<div class="mt-carousel-body">
<ul id="mt-carousel-scroll" class="mt-carousel-scroll">
${mediaItems.join("")}
</ul>
</div>
<button id="mt-carousel-prev" class="mt-carousel-prev" title="${
this.mtSettings.carouselPrevTxt
}" ${mediaClickedIndex === 1 ? "hidden" : ""}>
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24" aria-hidden="true">
<path d="M560-240 320-480l240-240 56 56-184 184 184 184-56 56Z" fill="var(--mt-color-content-txt)"></path>
</svg>
</button>
<button id="mt-carousel-next" class="mt-carousel-next" title="${
this.mtSettings.carouselNextTxt
}" ${mediaClickedIndex === mediaSiblings.length ? "hidden" : ""}>
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24" aria-hidden="true">
<path d="M504-480 320-664l56-56 240 240-240 240-56-56 184-184Z" fill="var(--mt-color-content-txt)"></path>
</svg>
</button>
`;
// Call dialog/modal with carousel "id" and HTML content
this.#openDialog("mt-carousel", carouselHTML);
// Set carousel interactions for horizontal scroll and buttons
if (mediaItems.length >= 2) {
this.#setCarouselInteractions(mediaSiblings.length, mediaClickedIndex);
}
}
/**
* Add interactions for the carousel
* @param {number} t Total number of medias loaded
* @param {number} m Index position of media clicked by user
*/
#setCarouselInteractions(t, m) {
let currentMediaIndex = m;
const carousel = document.getElementById("mt-carousel-scroll");
let scrollTimeout = 0;
let userScrolling = false;
const prevBtn = document.getElementById("mt-carousel-prev");
const nextBtn = document.getElementById("mt-carousel-next");
// Scroll the carusel to the media element
const scrollCarouselTo = (i, behavior = "smooth") => {
document
.getElementById("mt-carousel-" + i)
.scrollIntoView({ behavior: behavior });
};
// First run, place the scroll on clicked media
scrollCarouselTo(currentMediaIndex, "instant");
// Get current index of the media shown on screen
const updateMediaIndex = () => {
const scrolledMedia =
(carousel.scrollLeft + carousel.clientWidth) / carousel.clientWidth;
return Math.round(scrolledMedia + Number.EPSILON);
};
// Scroll interactions
const isScrolling = () => {
clearTimeout(scrollTimeout);
scrollTimeout = setTimeout(() => {
if (userScrolling) {
currentMediaIndex = updateMediaIndex();
checkBtnsVisibility();
}
userScrolling = true;
}, 60);
};
carousel.addEventListener("scroll", isScrolling);
// Click interactions
const checkBtnsVisibility = () => {
prevBtn.hidden = currentMediaIndex === 1;
nextBtn.hidden = currentMediaIndex === t;
};
const userClick = (e) => {
const idTarget = e.target.closest("button")?.id;
// Prev/next buttons
if (idTarget === "mt-carousel-next") {
userScrolling = false;
++currentMediaIndex;
if (currentMediaIndex > t) currentMediaIndex = t;
scrollCarouselTo(currentMediaIndex);
checkBtnsVisibility();
} else if (idTarget === "mt-carousel-prev") {
userScrolling = false;
--currentMediaIndex;
if (currentMediaIndex < 1) currentMediaIndex = 1;
scrollCarouselTo(currentMediaIndex);
checkBtnsVisibility();
}
// Close button
if (idTarget === "mt-carousel-close") {
killEventListeners();
}
};
document.addEventListener("click", userClick);
// Keyboard interactions
const userKeyDown = (e) => {
if (e.key === "Escape" || e.keyCode === 27) {
killEventListeners();
}
};
document.addEventListener("keydown", userKeyDown);
// Kill carousel listeners
const killEventListeners = () => {
carousel.removeEventListener("scroll", isScrolling);
document.removeEventListener("click", userClick);
document.removeEventListener("keydown", userKeyDown);
};
}
/**
* Replace the video preview image by the video player
* @param {event} e User interaction trigger
*/
#loadPostVideo(e) {
const parentNode = e.target.closest("[data-video-url]");
const videoUrl = parentNode.dataset.videoUrl;
const parentNode = e.target.closest("[data-media-type]");
const urlVideo = parentNode.dataset.mediaUrlHd;
parentNode.replaceChildren();
parentNode.innerHTML =
'<video controls src="' + videoUrl + '" autoplay></video>';
parentNode.innerHTML = `<video controls src="${urlVideo}" autoplay loop></video>`;
}
/**
@ -924,28 +1181,29 @@ export class Init {
* @param {event} e User interaction trigger
*/
#toogleSpoiler(e) {
const nextSibling = e.target.nextSibling;
const target = e.target;
const nextSibling = target.nextSibling;
if (
nextSibling.localName === "img" ||
nextSibling.localName === "audio" ||
nextSibling.localName === "video"
) {
e.target.parentNode.classList.remove("mt-post-media-spoiler");
e.target.style.display = "none";
target.parentNode.classList.remove("mt-post-media-spoiler");
target.style.display = "none";
} else if (
nextSibling.classList.contains("spoiler-txt-hidden") ||
nextSibling.classList.contains("spoiler-txt-visible")
) {
if (e.target.textContent == this.mtSettings.btnShowMore) {
if (target.textContent == this.mtSettings.btnShowMore) {
nextSibling.classList.remove("spoiler-txt-hidden");
nextSibling.classList.add("spoiler-txt-visible");
e.target.setAttribute("aria-expanded", "true");
e.target.textContent = this.mtSettings.btnShowLess;
target.setAttribute("aria-expanded", "true");
target.textContent = this.mtSettings.btnShowLess;
} else {
nextSibling.classList.remove("spoiler-txt-visible");
nextSibling.classList.add("spoiler-txt-hidden");
e.target.setAttribute("aria-expanded", "false");
e.target.textContent = this.mtSettings.btnShowMore;
target.setAttribute("aria-expanded", "false");
target.textContent = this.mtSettings.btnShowMore;
}
}
}
@ -966,7 +1224,7 @@ export class Init {
'"><img src="' +
c.image +
'" alt="' +
this.#escapeHtml(c.image_description) +
this.#escapeHTML(c.image_description) +
'" loading="lazy" /></div>'
: '<div class="mt-post-preview-noImage">📄</div>') +
"</div>" +
@ -1032,25 +1290,26 @@ export class Init {
} else if (this.mtSettings.timelineType === "local") {
btnSeeMorePath = "public/local";
}
const btnSeeMoreHTML =
'<a class="mt-btn-violet btn-see-more" href="' +
this.mtSettings.instanceUrl +
"/" +
this.#escapeHtml(btnSeeMorePath) +
'" rel="nofollow noopener noreferrer" target="_blank">' +
this.mtSettings.btnSeeMore +
"</a>";
const btnSeeMoreHTML = `
<a class="mt-btn-violet btn-see-more" href="${
this.mtSettings.instanceUrl
}/${this.#escapeHTML(
btnSeeMorePath
)}" rel="nofollow noopener noreferrer" target="_blank">
${this.mtSettings.btnSeeMore}
</a>`;
containerFooter.insertAdjacentHTML("beforeend", btnSeeMoreHTML);
}
// Create button to refresh the timeline
if (this.mtSettings.btnReload) {
const btnReloadHTML =
'<button class="mt-btn-violet btn-refresh">' +
'<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"><path d="M21 3v5m0 0h-5m5 0l-3-2.708C16.408 3.867 14.305 3 12 3a9 9 0 1 0 0 18c4.283 0 7.868-2.992 8.777-7" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>' +
this.mtSettings.btnReload +
"</button>";
const btnReloadHTML = `
<button class="mt-btn-violet btn-refresh">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"><path d="M21 3v5m0 0h-5m5 0l-3-2.708C16.408 3.867 14.305 3 12 3a9 9 0 1 0 0 18c4.283 0 7.868-2.992 8.777-7" stroke="var(--mt-color-btn-txt)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
${this.mtSettings.btnReload}
</button>`;
containerFooter.insertAdjacentHTML("beforeend", btnReloadHTML);
@ -1068,39 +1327,57 @@ export class Init {
*/
#setPostsInteracion() {
this.mtBodyNode.addEventListener("click", (e) => {
const target = e.target;
const localName = target.localName;
const parentNode = target.parentNode;
// Check if post cointainer was clicked
if (
e.target.localName == "article" ||
e.target.offsetParent?.localName == "article" ||
(e.target.localName == "img" &&
!e.target.parentNode.getAttribute("data-video-url"))
localName == "article" ||
target.offsetParent?.localName == "article" ||
(localName == "img" &&
this.mtSettings.disableCarousel &&
parentNode.getAttribute("data-media-type") !== "video" &&
parentNode.getAttribute("data-media-type") !== "gifv")
) {
this.#openPostUrl(e);
}
// Check if Show More/Less button was clicked
if (
e.target.localName == "button" &&
e.target.classList.contains("mt-btn-spoiler")
localName == "button" &&
target.classList.contains("mt-btn-spoiler")
) {
this.#toogleSpoiler(e);
}
// Check if image in post was clicked
if (
localName == "img" &&
!this.mtSettings.disableCarousel &&
parentNode.getAttribute("data-media-type") !== "video" &&
parentNode.getAttribute("data-media-type") !== "gifv"
) {
this.#buildCarousel(e);
}
// Check if video preview image or play icon/button was clicked
if (
e.target.className == "mt-post-media-play-icon" ||
(e.target.localName == "svg" &&
e.target.parentNode.className == "mt-post-media-play-icon") ||
(e.target.localName == "path" &&
e.target.parentNode.parentNode.className ==
"mt-post-media-play-icon") ||
(e.target.localName == "img" &&
e.target.parentNode.getAttribute("data-video-url"))
target.className == "mt-btn-play" ||
(localName == "svg" && parentNode.className == "mt-btn-play") ||
(localName == "path" &&
parentNode.parentNode.className == "mt-btn-play") ||
(localName == "img" &&
(parentNode.getAttribute("data-media-type") === "video" ||
parentNode.getAttribute("data-media-type") === "gifv"))
) {
this.#loadPostVideo(e);
}
});
this.mtBodyNode.addEventListener("keydown", (e) => {
const localName = e.target.localName;
// Check if Enter key was pressed with focus in an article
if (e.key === "Enter" && e.target.localName == "article") {
if (e.key === "Enter" && localName == "article") {
this.#openPostUrl(e);
}
});
@ -1118,6 +1395,7 @@ export class Init {
e.target.localName !== "button" &&
e.target.localName !== "bdi" &&
e.target.localName !== "time" &&
!e.target.classList.contains("mt-post-media-spoiler") &&
e.target.className !== "mt-post-preview-noImage" &&
e.target.parentNode.className !== "mt-post-avatar-image-big" &&
e.target.parentNode.className !== "mt-post-avatar-image-small" &&
@ -1155,12 +1433,12 @@ export class Init {
*/
#showError(t, i) {
const icon = i || "❌";
this.mtBodyNode.innerHTML =
'<div class="mt-error"><span class="mt-error-icon">' +
icon +
'</span><br/><strong>Oops, something\'s happened:</strong><br/><div class="mt-error-message">' +
t +
"</div></div>";
this.mtBodyNode.innerHTML = `
<div class="mt-error">
<span class="mt-error-icon">${icon}</span>
<strong>Oops, something's happened:</strong>
<div class="mt-error-message">${t}</div>
</div>`;
this.mtBodyNode.setAttribute("role", "none");
throw new Error(
"Stopping the script due to an error building the timeline."