Merge branch 'feature/sanitize-content' into 'master'

Feature/sanitize content

See merge request idotj/mastodon-embed-timeline!36
This commit is contained in:
i.j 2024-04-04 11:33:44 +00:00
commit f945026b16
9 changed files with 252 additions and 146 deletions

View File

@ -1,17 +0,0 @@
image: node:latest
stages:
- deploy
variables:
REGISTRY_URL: "//${CI_SERVER_HOST}/api/v4/projects/${CI_PROJECT_ID}/packages/npm/"
deploy:
stage: deploy
script:
- echo "@scope:registry=https:${REGISTRY_URL}" > .npmrc
- echo "${REGISTRY_URL}:_authToken=${CI_JOB_TOKEN}" >> .npmrc
- npm publish
only:
- master
environment: production

View File

@ -1,3 +1,9 @@
v4.4.1 - 04/04/2024
- Fix render emojos in warning/spoiler text
- Sanitize post content before rendering
- Add custom title for play video button
- Update Rollup devDependency version
v4.3.12 - 26/03/2024 v4.3.12 - 26/03/2024
- Add button to hide sensitive/spoiler media - Add button to hide sensitive/spoiler media
- Fix Refresh button bug when empty text - Fix Refresh button bug when empty text

View File

@ -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: Copy the following CSS and JS links to include them in your project:
```html ```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@idotj/mastodon-embed-timeline@4.3.12/dist/mastodon-timeline.min.css" integrity="sha256-1UGgxsonaMCfOEnVOL89aMKSo3GEAmaRP0ISbsWa6lU=" crossorigin="anonymous"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@idotj/mastodon-embed-timeline@4.4.1/dist/mastodon-timeline.min.css" crossorigin="anonymous">
``` ```
```html ```html
<script src="https://cdn.jsdelivr.net/npm/@idotj/mastodon-embed-timeline@4.3.12/dist/mastodon-timeline.umd.js" integrity="sha256-OrmppdyvemrOyZys4HyoXGqcaH70LbJFE7rya+glr2Q=" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/@idotj/mastodon-embed-timeline@4.4.1/dist/mastodon-timeline.umd.js" crossorigin="anonymous"></script>
``` ```
### Package manager ### Package manager
@ -273,13 +273,16 @@ Here you have all the options available to quickly setup and customize your time
// Default: false (don't hide) // Default: false (don't hide)
hideEmojos: false, hideEmojos: false,
// Customize the text of the button used for showing sensitive/spoiler media content // Customize the text of the button used for showing a sensitive/spoiler media content
btnShowContent: "SHOW CONTENT", btnShowContent: "SHOW CONTENT",
// Hide video image preview and load video player instead // Hide video image preview and load the video player instead
// Default: false (don't hide) // Default: false (don't hide)
hideVideoPreview: false, hideVideoPreview: false,
// Customize the text of the button used for the image preview to play the video
btnPlayVideoTxt: "Load and play video",
// Hide preview card if post contains a link, photo or video from a Url // Hide preview card if post contains a link, photo or video from a Url
// Default: false (don't hide) // Default: false (don't hide)
hidePreviewLink: false, hidePreviewLink: false,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

144
package-lock.json generated
View File

@ -1,17 +1,17 @@
{ {
"name": "@idotj/mastodon-embed-timeline", "name": "@idotj/mastodon-embed-timeline",
"version": "4.3.12", "version": "4.4.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@idotj/mastodon-embed-timeline", "name": "@idotj/mastodon-embed-timeline",
"version": "4.3.12", "version": "4.4.1",
"license": "GNU", "license": "GNU",
"devDependencies": { "devDependencies": {
"@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-terser": "^0.4.4",
"clean-css-cli": "^5.6.3", "clean-css-cli": "^5.6.3",
"rollup": "^4.12.0" "rollup": "^4.14.0"
} }
}, },
"node_modules/@jridgewell/gen-mapping": { "node_modules/@jridgewell/gen-mapping": {
@ -95,9 +95,9 @@
} }
}, },
"node_modules/@rollup/rollup-android-arm-eabi": { "node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.12.0", "version": "4.14.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.12.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.14.0.tgz",
"integrity": "sha512-+ac02NL/2TCKRrJu2wffk1kZ+RyqxVUlbjSagNgPm94frxtr+XDL12E5Ll1enWskLrtrZ2r8L3wED1orIibV/w==", "integrity": "sha512-jwXtxYbRt1V+CdQSy6Z+uZti7JF5irRKF8hlKfEnF/xJpcNGuuiZMBvuoYM+x9sr9iWGnzrlM0+9hvQ1kgkf1w==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@ -108,9 +108,9 @@
] ]
}, },
"node_modules/@rollup/rollup-android-arm64": { "node_modules/@rollup/rollup-android-arm64": {
"version": "4.12.0", "version": "4.14.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.12.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.14.0.tgz",
"integrity": "sha512-OBqcX2BMe6nvjQ0Nyp7cC90cnumt8PXmO7Dp3gfAju/6YwG0Tj74z1vKrfRz7qAv23nBcYM8BCbhrsWqO7PzQQ==", "integrity": "sha512-fI9nduZhCccjzlsA/OuAwtFGWocxA4gqXGTLvOyiF8d+8o0fZUeSztixkYjcGq1fGZY3Tkq4yRvHPFxU+jdZ9Q==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -121,9 +121,9 @@
] ]
}, },
"node_modules/@rollup/rollup-darwin-arm64": { "node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.12.0", "version": "4.14.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.12.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.14.0.tgz",
"integrity": "sha512-X64tZd8dRE/QTrBIEs63kaOBG0b5GVEd3ccoLtyf6IdXtHdh8h+I56C2yC3PtC9Ucnv0CpNFJLqKFVgCYe0lOQ==", "integrity": "sha512-BcnSPRM76/cD2gQC+rQNGBN6GStBs2pl/FpweW8JYuz5J/IEa0Fr4AtrPv766DB/6b2MZ/AfSIOSGw3nEIP8SA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -134,9 +134,9 @@
] ]
}, },
"node_modules/@rollup/rollup-darwin-x64": { "node_modules/@rollup/rollup-darwin-x64": {
"version": "4.12.0", "version": "4.14.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.12.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.14.0.tgz",
"integrity": "sha512-cc71KUZoVbUJmGP2cOuiZ9HSOP14AzBAThn3OU+9LcA1+IUqswJyR1cAJj3Mg55HbjZP6OLAIscbQsQLrpgTOg==", "integrity": "sha512-LDyFB9GRolGN7XI6955aFeI3wCdCUszFWumWU0deHA8VpR3nWRrjG6GtGjBrQxQKFevnUTHKCfPR4IvrW3kCgQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -147,9 +147,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm-gnueabihf": { "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.12.0", "version": "4.14.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.12.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.14.0.tgz",
"integrity": "sha512-a6w/Y3hyyO6GlpKL2xJ4IOh/7d+APaqLYdMf86xnczU3nurFTaVN9s9jOXQg97BE4nYm/7Ga51rjec5nfRdrvA==", "integrity": "sha512-ygrGVhQP47mRh0AAD0zl6QqCbNsf0eTo+vgwkY6LunBcg0f2Jv365GXlDUECIyoXp1kKwL5WW6rsO429DBY/bA==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@ -160,9 +160,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm64-gnu": { "node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.12.0", "version": "4.14.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.12.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.14.0.tgz",
"integrity": "sha512-0fZBq27b+D7Ar5CQMofVN8sggOVhEtzFUwOwPppQt0k+VR+7UHMZZY4y+64WJ06XOhBTKXtQB/Sv0NwQMXyNAA==", "integrity": "sha512-x+uJ6MAYRlHGe9wi4HQjxpaKHPM3d3JjqqCkeC5gpnnI6OWovLdXTpfa8trjxPLnWKyBsSi5kne+146GAxFt4A==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -173,9 +173,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm64-musl": { "node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.12.0", "version": "4.14.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.12.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.14.0.tgz",
"integrity": "sha512-eTvzUS3hhhlgeAv6bfigekzWZjaEX9xP9HhxB0Dvrdbkk5w/b+1Sxct2ZuDxNJKzsRStSq1EaEkVSEe7A7ipgQ==", "integrity": "sha512-nrRw8ZTQKg6+Lttwqo6a2VxR9tOroa2m91XbdQ2sUUzHoedXlsyvY1fN4xWdqz8PKmf4orDwejxXHjh7YBGUCA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -185,10 +185,23 @@
"linux" "linux"
] ]
}, },
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.14.0.tgz",
"integrity": "sha512-xV0d5jDb4aFu84XKr+lcUJ9y3qpIWhttO3Qev97z8DKLXR62LC3cXT/bMZXrjLF9X+P5oSmJTzAhqwUbY96PnA==",
"cpu": [
"ppc64le"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": { "node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.12.0", "version": "4.14.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.12.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.14.0.tgz",
"integrity": "sha512-ix+qAB9qmrCRiaO71VFfY8rkiAZJL8zQRXveS27HS+pKdjwUfEhqo2+YF2oI+H/22Xsiski+qqwIBxVewLK7sw==", "integrity": "sha512-SDDhBQwZX6LPRoPYjAZWyL27LbcBo7WdBFWJi5PI9RPCzU8ijzkQn7tt8NXiXRiFMJCVpkuMkBf4OxSxVMizAw==",
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
@ -198,10 +211,23 @@
"linux" "linux"
] ]
}, },
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.14.0.tgz",
"integrity": "sha512-RxB/qez8zIDshNJDufYlTT0ZTVut5eCpAZ3bdXDU9yTxBzui3KhbGjROK2OYTTor7alM7XBhssgoO3CZ0XD3qA==",
"cpu": [
"s390x"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": { "node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.12.0", "version": "4.14.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.12.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.14.0.tgz",
"integrity": "sha512-TenQhZVOtw/3qKOPa7d+QgkeM6xY0LtwzR8OplmyL5LrgTWIXpTQg2Q2ycBf8jm+SFW2Wt/DTn1gf7nFp3ssVA==", "integrity": "sha512-C6y6z2eCNCfhZxT9u+jAM2Fup89ZjiG5pIzZIDycs1IwESviLxwkQcFRGLjnDrP+PT+v5i4YFvlcfAs+LnreXg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -212,9 +238,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-x64-musl": { "node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.12.0", "version": "4.14.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.12.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.14.0.tgz",
"integrity": "sha512-LfFdRhNnW0zdMvdCb5FNuWlls2WbbSridJvxOvYWgSBOYZtgBfW9UGNJG//rwMqTX1xQE9BAodvMH9tAusKDUw==", "integrity": "sha512-i0QwbHYfnOMYsBEyjxcwGu5SMIi9sImDVjDg087hpzXqhBSosxkE7gyIYFHgfFl4mr7RrXksIBZ4DoLoP4FhJg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -225,9 +251,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-arm64-msvc": { "node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.12.0", "version": "4.14.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.12.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.14.0.tgz",
"integrity": "sha512-JPDxovheWNp6d7AHCgsUlkuCKvtu3RB55iNEkaQcf0ttsDU/JZF+iQnYcQJSk/7PtT4mjjVG8N1kpwnI9SLYaw==", "integrity": "sha512-Fq52EYb0riNHLBTAcL0cun+rRwyZ10S9vKzhGKKgeD+XbwunszSY0rVMco5KbOsTlwovP2rTOkiII/fQ4ih/zQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -238,9 +264,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-ia32-msvc": { "node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.12.0", "version": "4.14.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.12.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.14.0.tgz",
"integrity": "sha512-fjtuvMWRGJn1oZacG8IPnzIV6GF2/XG+h71FKn76OYFqySXInJtseAqdprVTDTyqPxQOG9Exak5/E9Z3+EJ8ZA==", "integrity": "sha512-e/PBHxPdJ00O9p5Ui43+vixSgVf4NlLsmV6QneGERJ3lnjIua/kim6PRFe3iDueT1rQcgSkYP8ZBBXa/h4iPvw==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
@ -251,9 +277,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-x64-msvc": { "node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.12.0", "version": "4.14.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.12.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.14.0.tgz",
"integrity": "sha512-ZYmr5mS2wd4Dew/JjT0Fqi2NPB/ZhZ2VvPp7SmvPZb4Y1CG/LRcS6tcRo2cYU7zLK5A7cdbhWnnWmUjoI4qapg==", "integrity": "sha512-aGg7iToJjdklmxlUlJh/PaPNa4PmqHfyRMLunbL3eaMO0gp656+q1zOKkpJ/CVe9CryJv6tAN1HDoR8cNGzkag==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -601,9 +627,9 @@
} }
}, },
"node_modules/rollup": { "node_modules/rollup": {
"version": "4.12.0", "version": "4.14.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.14.0.tgz",
"integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==", "integrity": "sha512-Qe7w62TyawbDzB4yt32R0+AbIo6m1/sqO7UPzFS8Z/ksL5mrfhA0v4CavfdmFav3D+ub4QeAgsGEe84DoWe/nQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/estree": "1.0.5" "@types/estree": "1.0.5"
@ -616,19 +642,21 @@
"npm": ">=8.0.0" "npm": ">=8.0.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.12.0", "@rollup/rollup-android-arm-eabi": "4.14.0",
"@rollup/rollup-android-arm64": "4.12.0", "@rollup/rollup-android-arm64": "4.14.0",
"@rollup/rollup-darwin-arm64": "4.12.0", "@rollup/rollup-darwin-arm64": "4.14.0",
"@rollup/rollup-darwin-x64": "4.12.0", "@rollup/rollup-darwin-x64": "4.14.0",
"@rollup/rollup-linux-arm-gnueabihf": "4.12.0", "@rollup/rollup-linux-arm-gnueabihf": "4.14.0",
"@rollup/rollup-linux-arm64-gnu": "4.12.0", "@rollup/rollup-linux-arm64-gnu": "4.14.0",
"@rollup/rollup-linux-arm64-musl": "4.12.0", "@rollup/rollup-linux-arm64-musl": "4.14.0",
"@rollup/rollup-linux-riscv64-gnu": "4.12.0", "@rollup/rollup-linux-powerpc64le-gnu": "4.14.0",
"@rollup/rollup-linux-x64-gnu": "4.12.0", "@rollup/rollup-linux-riscv64-gnu": "4.14.0",
"@rollup/rollup-linux-x64-musl": "4.12.0", "@rollup/rollup-linux-s390x-gnu": "4.14.0",
"@rollup/rollup-win32-arm64-msvc": "4.12.0", "@rollup/rollup-linux-x64-gnu": "4.14.0",
"@rollup/rollup-win32-ia32-msvc": "4.12.0", "@rollup/rollup-linux-x64-musl": "4.14.0",
"@rollup/rollup-win32-x64-msvc": "4.12.0", "@rollup/rollup-win32-arm64-msvc": "4.14.0",
"@rollup/rollup-win32-ia32-msvc": "4.14.0",
"@rollup/rollup-win32-x64-msvc": "4.14.0",
"fsevents": "~2.3.2" "fsevents": "~2.3.2"
} }
}, },

View File

@ -1,6 +1,6 @@
{ {
"name": "@idotj/mastodon-embed-timeline", "name": "@idotj/mastodon-embed-timeline",
"version": "4.3.12", "version": "4.4.1",
"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.", "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", "license": "GNU",
"author": { "author": {
@ -27,9 +27,6 @@
"main": "dist/mastodon-timeline.esm.js", "main": "dist/mastodon-timeline.esm.js",
"types": "dist/mastodon-timeline.d.ts", "types": "dist/mastodon-timeline.d.ts",
"style": "dist/mastodon-timeline.min.css", "style": "dist/mastodon-timeline.min.css",
"publishConfig": {
"registry": "https://gitlab.com/api/v4/projects/25689941/packages/npm/"
},
"scripts": { "scripts": {
"test": "echo \"No test specified\" && exit 1", "test": "echo \"No test specified\" && exit 1",
"build": "npm run build:minifyCss && rollup -c", "build": "npm run build:minifyCss && rollup -c",
@ -37,7 +34,7 @@
}, },
"devDependencies": { "devDependencies": {
"clean-css-cli": "^5.6.3", "clean-css-cli": "^5.6.3",
"rollup": "^4.12.0", "rollup": "^4.14.0",
"@rollup/plugin-terser": "^0.4.4" "@rollup/plugin-terser": "^0.4.4"
}, },
"files": [ "files": [

View File

@ -1,4 +1,4 @@
/* Mastodon embed timeline v4.3.12 */ /* Mastodon embed timeline v4.4.1 */
/* More info at: */ /* More info at: */
/* https://gitlab.com/idotj/mastodon-embed-timeline */ /* https://gitlab.com/idotj/mastodon-embed-timeline */

View File

@ -1,7 +1,7 @@
/** /**
* Mastodon embed timeline * Mastodon embed timeline
* @author idotj * @author idotj
* @version 4.3.12 * @version 4.4.1
* @url https://gitlab.com/idotj/mastodon-embed-timeline * @url https://gitlab.com/idotj/mastodon-embed-timeline
* @license GNU AGPLv3 * @license GNU AGPLv3
*/ */
@ -38,6 +38,7 @@ export class Init {
hideEmojos: false, hideEmojos: false,
btnShowContent: "SHOW CONTENT", btnShowContent: "SHOW CONTENT",
hideVideoPreview: false, hideVideoPreview: false,
btnPlayVideoTxt: "Load and play video",
hidePreviewLink: false, hidePreviewLink: false,
previewMaxLines: "", previewMaxLines: "",
hideCounterBar: false, hideCounterBar: false,
@ -82,7 +83,7 @@ export class Init {
/** /**
* Trigger callback when DOM loaded or completed * Trigger callback when DOM loaded or completed
* @param {function} c Callback executed * @param {Function} c Callback executed
*/ */
#onDOMContentLoaded(c) { #onDOMContentLoaded(c) {
if (typeof document !== "undefined" && document.readyState === "complete") { if (typeof document !== "undefined" && document.readyState === "complete") {
@ -165,7 +166,7 @@ export class Init {
/** /**
* Apply the color theme in the timeline * Apply the color theme in the timeline
* @param {string} themeType Type of color theme ('light' or 'dark') * @param {String} themeType Type of color theme ('light' or 'dark')
*/ */
mtColorTheme(themeType) { mtColorTheme(themeType) {
this.#onDOMContentLoaded(() => { this.#onDOMContentLoaded(() => {
@ -193,7 +194,7 @@ export class Init {
/** /**
* Requests to the server to collect all the data * Requests to the server to collect all the data
* @returns {object} Data container * @returns {Object} Data container
*/ */
#getTimelineData() { #getTimelineData() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -256,8 +257,8 @@ export class Init {
/** /**
* Set all urls before fetching the data * Set all urls before fetching the data
* @param {string} Instance url api * @param {String} Instance url api
* @returns {object} * @returns {Object}
*/ */
#setUrls(i) { #setUrls(i) {
let urls = {}; let urls = {};
@ -301,9 +302,9 @@ export class Init {
/** /**
* Fetch data from server * Fetch data from server
* @param {string} u Url address to fetch * @param {String} u Url address to fetch
* @param {boolean} h gets the link header * @param {Boolean} h gets the link header
* @returns {array} List of objects * @returns {Array} List of objects
*/ */
async #fetchData(u, h = false) { async #fetchData(u, h = false) {
const response = await fetch(u); const response = await fetch(u);
@ -326,7 +327,7 @@ export class Init {
/** /**
* Check if there are enough posts to reach the value of maxNbPostFetch * Check if there are enough posts to reach the value of maxNbPostFetch
* @returns {boolean} * @returns {Boolean}
*/ */
#isNbPostsFulfilled() { #isNbPostsFulfilled() {
return ( return (
@ -352,8 +353,8 @@ export class Init {
/** /**
* Parse link header into an object * Parse link header into an object
* @param {string} l Link header * @param {String} l Link header
* @returns {object} * @returns {Object}
*/ */
#parseLinkHeader(l) { #parseLinkHeader(l) {
const linkArray = l.split(", ").map((header) => header.split("; ")); const linkArray = l.split(", ").map((header) => header.split("; "));
@ -367,7 +368,7 @@ export class Init {
/** /**
* Filter all fetched posts and append them on the timeline * Filter all fetched posts and append them on the timeline
* @param {string} t Type of build (new or reload) * @param {String} t Type of build (new or reload)
*/ */
async #buildTimeline(t) { async #buildTimeline(t) {
await this.#getTimelineData(); await this.#getTimelineData();
@ -446,7 +447,7 @@ export class Init {
/** /**
* Add the attribute Aria-setsize to all posts * Add the attribute Aria-setsize to all posts
* @param {number} n The total number of posts showed in the timeline * @param {Number} n The total number of posts showed in the timeline
*/ */
#addAriaSetsize(n) { #addAriaSetsize(n) {
const articles = this.mtBodyNode.getElementsByTagName("article"); const articles = this.mtBodyNode.getElementsByTagName("article");
@ -458,8 +459,8 @@ export class Init {
/** /**
* Add each post in the timeline container * Add each post in the timeline container
* @param {object} c Post content * @param {Object} c Post content
* @param {number} i Index of post * @param {Number} i Index of post
*/ */
#appendPost(c, i) { #appendPost(c, i) {
this.mtBodyNode.insertAdjacentHTML("beforeend", this.#assamblePost(c, i)); this.mtBodyNode.insertAdjacentHTML("beforeend", this.#assamblePost(c, i));
@ -467,8 +468,8 @@ export class Init {
/** /**
* Build post structure * Build post structure
* @param {object} c Post content * @param {Object} c Post content
* @param {number} i Index of post * @param {Number} i Index of post
*/ */
#assamblePost(c, i) { #assamblePost(c, i) {
let avatar, let avatar,
@ -641,7 +642,7 @@ export class Init {
if (c.spoiler_text !== "") { if (c.spoiler_text !== "") {
content = content =
'<div class="mt-post-txt">' + '<div class="mt-post-txt">' +
c.spoiler_text + this.#formatPostText(c.spoiler_text) +
' <button type="button" class="mt-btn-dark mt-btn-spoiler-txt" aria-expanded="false">' + ' <button type="button" class="mt-btn-dark mt-btn-spoiler-txt" aria-expanded="false">' +
this.mtSettings.btnShowMore + this.mtSettings.btnShowMore +
"</button>" + "</button>" +
@ -656,7 +657,7 @@ export class Init {
) { ) {
content = content =
'<div class="mt-post-txt">' + '<div class="mt-post-txt">' +
c.reblog.spoiler_text + this.#formatPostText(c.reblog.spoiler_text) +
' <button type="button" class="mt-btn-dark mt-btn-spoiler-txt" aria-expanded="false">' + ' <button type="button" class="mt-btn-dark mt-btn-spoiler-txt" aria-expanded="false">' +
this.mtSettings.btnShowMore + this.mtSettings.btnShowMore +
"</button>" + "</button>" +
@ -757,7 +758,7 @@ export class Init {
"</div>"; "</div>";
} }
// Add all to main post container // Put all elements together in the post container
const post = const post =
'<article class="mt-post" aria-posinset="' + '<article class="mt-post" aria-posinset="' +
(i + 1) + (i + 1) +
@ -779,14 +780,98 @@ export class Init {
return post; return post;
} }
/**
* Sanitize an HTML string
* (c) Chris Ferdinandi, MIT License, https://gomakethings.com
* @param {String} s The HTML string to sanitize
* @param {Boolean} n If true, returns HTML nodes instead of a string
* @return {String|NodeList} The sanitized string or nodes
*/
#cleanHTML(s, n) {
/**
* Convert the string to an HTML document
* @return {Node} An HTML document
*/
function stringToHTML() {
let parser = new DOMParser();
let doc = parser.parseFromString(s, "text/html");
return doc.body || document.createElement("body");
}
/**
* Remove <script> elements
* @param {Node} html The HTML
*/
function removeScripts(html) {
let scripts = html.querySelectorAll("script");
for (let script of scripts) {
script.remove();
}
}
/**
* Check if the attribute is potentially dangerous
* @param {String} name The attribute name
* @param {String} value The attribute value
* @return {Boolean} If true, the attribute is potentially dangerous
*/
function isPossiblyDangerous(name, value) {
let val = value.replace(/\s+/g, "").toLowerCase();
if (["src", "href", "xlink:href"].includes(name)) {
if (val.includes("javascript:") || val.includes("data:")) return true;
}
if (name.startsWith("on")) return true;
}
/**
* Remove potentially dangerous attributes from an element
* @param {Node} elem The element
*/
function removeAttributes(elem) {
// Loop through each attribute
// If it's dangerous, remove it
let atts = elem.attributes;
for (let { name, value } of atts) {
if (!isPossiblyDangerous(name, value)) continue;
elem.removeAttribute(name);
}
}
/**
* Remove dangerous stuff from the HTML document's nodes
* @param {Node} html The HTML document
*/
function clean(html) {
let nodes = html.children;
for (let node of nodes) {
removeAttributes(node);
clean(node);
}
}
// Convert the string to HTML
let html = stringToHTML();
// Sanitize it
removeScripts(html);
clean(html);
// If the user wants HTML nodes back, return them
// Otherwise, pass a sanitized string back
return n ? html.childNodes : html.innerHTML;
}
/** /**
* Handle text changes made to posts * Handle text changes made to posts
* @param {string} c Text content * @param {String} c Text content
* @returns {string} Text content modified * @returns {String} Text content modified
*/ */
#formatPostText(c) { #formatPostText(c) {
let content = c; let content = c;
// Sanitize string
content = this.#cleanHTML(content, false);
// Format hashtags and mentions // Format hashtags and mentions
content = this.#addTarget2hashtagMention(content); content = this.#addTarget2hashtagMention(content);
@ -811,8 +896,8 @@ export class Init {
/** /**
* Add target="_blank" to all #hashtags and @mentions in the post * Add target="_blank" to all #hashtags and @mentions in the post
* @param {string} c Text content * @param {String} c Text content
* @returns {string} Text content modified * @returns {String} Text content modified
*/ */
#addTarget2hashtagMention(c) { #addTarget2hashtagMention(c) {
let content = c.replaceAll('rel="tag"', 'rel="tag" target="_blank"'); let content = c.replaceAll('rel="tag"', 'rel="tag" target="_blank"');
@ -826,12 +911,12 @@ export class Init {
/** /**
* Find all start/end <tags> and replace them by another start/end <tags> * Find all start/end <tags> and replace them by another start/end <tags>
* @param {string} c Text content * @param {String} c Text content
* @param {string} initialTagOpen Start HTML tag to replace * @param {String} initialTagOpen Start HTML tag to replace
* @param {string} initialTagClose End HTML tag to replace * @param {String} initialTagClose End HTML tag to replace
* @param {string} replacedTagOpen New start HTML tag * @param {String} replacedTagOpen New start HTML tag
* @param {string} replacedTagClose New end HTML tag * @param {String} replacedTagClose New end HTML tag
* @returns {string} Text in HTML format * @returns {String} Text in HTML format
*/ */
#replaceHTMLtag( #replaceHTMLtag(
c, c,
@ -855,8 +940,8 @@ export class Init {
/** /**
* Escape quotes and other special characters, to make them safe to add * Escape quotes and other special characters, to make them safe to add
* to HTML content and attributes as plain text * to HTML content and attributes as plain text
* @param {string} s String * @param {String} s String
* @returns {string} String * @returns {String} String
*/ */
#escapeHTML(s) { #escapeHTML(s) {
return (s ?? "") return (s ?? "")
@ -869,9 +954,9 @@ export class Init {
/** /**
* Find all custom emojis shortcode and replace by image * Find all custom emojis shortcode and replace by image
* @param {string} c Text content * @param {String} c Text content
* @param {array} e List with all custom emojis * @param {Array} e List with all custom emojis
* @returns {string} Text content modified * @returns {String} Text content modified
*/ */
#shortcode2Emojos(c, e) { #shortcode2Emojos(c, e) {
if (c.includes(":")) { if (c.includes(":")) {
@ -891,8 +976,8 @@ export class Init {
/** /**
* Format date * Format date
* @param {string} d Date in ISO format (YYYY-MM-DDTHH:mm:ss.sssZ) * @param {String} d Date in ISO format (YYYY-MM-DDTHH:mm:ss.sssZ)
* @returns {string} Date formated * @returns {String} Date formated
*/ */
#formatDate(d) { #formatDate(d) {
const originalDate = new Date(d); const originalDate = new Date(d);
@ -907,9 +992,9 @@ export class Init {
/** /**
* Create media element * Create media element
* @param {object} m Media content * @param {Object} m Media content
* @param {boolean} s Sensitive/spoiler status * @param {Boolean} s Sensitive/spoiler status
* @returns {string} Media in HTML format * @returns {String} Media in HTML format
*/ */
#createMedia(m, s = false) { #createMedia(m, s = false) {
const type = m.type; const type = m.type;
@ -1019,7 +1104,9 @@ export class Init {
'" alt="' + '" alt="' +
(m.description ? this.#escapeHTML(m.description) : "") + (m.description ? this.#escapeHTML(m.description) : "") +
'" loading="lazy" />' + '" loading="lazy" />' +
'<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>' + '<button class="mt-btn-play" title="' +
this.mtSettings.btnPlayVideoTxt +
'"><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>"; "</div>";
} else { } else {
media = media =
@ -1051,8 +1138,8 @@ export class Init {
/** /**
* Open a dialog/modal with the styles of Mastodon timeline * Open a dialog/modal with the styles of Mastodon timeline
* @param {string} i Dialog Id name * @param {String} i Dialog Id name
* @param {string} c Dialog HTML content * @param {String} c Dialog HTML content
*/ */
#openDialog(i, c) { #openDialog(i, c) {
let dialog = document.createElement("dialog"); let dialog = document.createElement("dialog");
@ -1069,7 +1156,7 @@ export class Init {
/** /**
* Build a carousel/lightbox with the media content in the post clicked * Build a carousel/lightbox with the media content in the post clicked
* @param {event} e User interaction trigger * @param {Event} e User interaction trigger
*/ */
#showCarousel(e) { #showCarousel(e) {
// List all medias in the post and remove sensitive/spoiler medias // List all medias in the post and remove sensitive/spoiler medias
@ -1181,8 +1268,8 @@ export class Init {
/** /**
* Add interactions for the carousel * Add interactions for the carousel
* @param {number} t Total number of medias loaded * @param {Number} t Total number of medias loaded
* @param {number} m Index position of media clicked by user * @param {Number} m Index position of media clicked by user
*/ */
#setCarouselInteractions(t, m) { #setCarouselInteractions(t, m) {
let currentMediaIndex = m; let currentMediaIndex = m;
@ -1270,7 +1357,7 @@ export class Init {
/** /**
* Replace the video preview image by the video player * Replace the video preview image by the video player
* @param {event} e User interaction trigger * @param {Event} e User interaction trigger
*/ */
#loadPostVideo(e) { #loadPostVideo(e) {
const parentNode = e.target.closest("[data-media-type]"); const parentNode = e.target.closest("[data-media-type]");
@ -1281,7 +1368,7 @@ export class Init {
/** /**
* Spoiler toggle for text * Spoiler toggle for text
* @param {event} e User interaction trigger * @param {Event} e User interaction trigger
*/ */
#toogleTxtSpoiler(e) { #toogleTxtSpoiler(e) {
const target = e.target; const target = e.target;
@ -1302,7 +1389,7 @@ export class Init {
/** /**
* Spoiler toggle for image/video * Spoiler toggle for image/video
* @param {event} e User interaction trigger * @param {Event} e User interaction trigger
*/ */
#toogleMediaSpoiler(e) { #toogleMediaSpoiler(e) {
const target = e.target; const target = e.target;
@ -1315,8 +1402,8 @@ export class Init {
/** /**
* Create preview link * Create preview link
* @param {object} c Preview link content * @param {Object} c Preview link content
* @returns {string} Preview link in HTML format * @returns {String} Preview link in HTML format
*/ */
#createPreviewLink(c) { #createPreviewLink(c) {
let previewDescription = ""; let previewDescription = "";
@ -1369,8 +1456,8 @@ export class Init {
/** /**
* Parse HTML string * Parse HTML string
* @param {string} s HTML string * @param {String} s HTML string
* @returns {string} Plain text * @returns {String} Plain text
*/ */
#parseHTMLstring(s) { #parseHTMLstring(s) {
const parser = new DOMParser(); const parser = new DOMParser();
@ -1443,6 +1530,7 @@ export class Init {
*/ */
#addPostListener() { #addPostListener() {
this.mtBodyNode.addEventListener("click", (e) => { this.mtBodyNode.addEventListener("click", (e) => {
console.log("click on: ", e);
const target = e.target; const target = e.target;
const localName = target.localName; const localName = target.localName;
const parentNode = target.parentNode; const parentNode = target.parentNode;
@ -1487,6 +1575,7 @@ export class Init {
(parentNode.getAttribute("data-media-type") === "video" || (parentNode.getAttribute("data-media-type") === "video" ||
parentNode.getAttribute("data-media-type") === "gifv")) parentNode.getAttribute("data-media-type") === "gifv"))
) { ) {
console.log("loadPostVideo");
this.#loadPostVideo(e); this.#loadPostVideo(e);
} }
}); });
@ -1501,7 +1590,7 @@ export class Init {
/** /**
* Open post in a new tab/page avoiding any other natural link * Open post in a new tab/page avoiding any other natural link
* @param {event} e User interaction trigger * @param {Event} e User interaction trigger
*/ */
#openPostUrl(e) { #openPostUrl(e) {
const urlPost = e.target.closest(".mt-post").dataset.location; const urlPost = e.target.closest(".mt-post").dataset.location;
@ -1545,8 +1634,8 @@ export class Init {
/** /**
* Show an error on the timeline * Show an error on the timeline
* @param {string} e Error message * @param {String} e Error message
* @param {string} i Icon * @param {String} i Icon
*/ */
#showError(t, i) { #showError(t, i) {
const icon = i || "❌"; const icon = i || "❌";