Merge branch 'feature/lightbox-carousel' into 'master'
Feature/lightbox-carousel See merge request idotj/mastodon-embed-timeline!32
This commit is contained in:
commit
164c45bf9e
10
CHANGELOG
10
CHANGELOG
@ -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
|
||||
|
23
README.md
23
README.md
@ -2,7 +2,7 @@
|
||||
|
||||

|
||||
|
||||
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
|
||||
|
4
dist/mastodon-timeline.esm.js
vendored
4
dist/mastodon-timeline.esm.js
vendored
File diff suppressed because one or more lines are too long
2
dist/mastodon-timeline.min.css
vendored
2
dist/mastodon-timeline.min.css
vendored
File diff suppressed because one or more lines are too long
4
dist/mastodon-timeline.umd.js
vendored
4
dist/mastodon-timeline.umd.js
vendored
File diff suppressed because one or more lines are too long
@ -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
189
package-lock.json
generated
@ -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",
|
||||
|
@ -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": {
|
||||
|
@ -1,20 +1,20 @@
|
||||
import terser from "@rollup/plugin-terser";
|
||||
|
||||
export default [
|
||||
{
|
||||
input: "src/mastodon-timeline.js",
|
||||
output: [
|
||||
{
|
||||
file: "dist/mastodon-timeline.esm.js",
|
||||
format: "esm",
|
||||
sourcemap: false,
|
||||
},
|
||||
{
|
||||
name: "MastodonTimeline",
|
||||
file: "dist/mastodon-timeline.umd.js",
|
||||
format: "umd",
|
||||
},
|
||||
],
|
||||
plugins: [terser()],
|
||||
},
|
||||
];
|
||||
import terser from "@rollup/plugin-terser";
|
||||
|
||||
export default [
|
||||
{
|
||||
input: "src/mastodon-timeline.js",
|
||||
output: [
|
||||
{
|
||||
file: "dist/mastodon-timeline.esm.js",
|
||||
format: "esm",
|
||||
sourcemap: false,
|
||||
},
|
||||
{
|
||||
name: "MastodonTimeline",
|
||||
file: "dist/mastodon-timeline.umd.js",
|
||||
format: "umd",
|
||||
},
|
||||
],
|
||||
plugins: [terser()],
|
||||
},
|
||||
];
|
||||
|
@ -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%;
|
||||
|
@ -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("&", "&")
|
||||
.replaceAll("<", "<")
|
||||
@ -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."
|
||||
|
Loading…
x
Reference in New Issue
Block a user