From 580fd5809b1d09e71a30a2ec4ae87a0676177756 Mon Sep 17 00:00:00 2001 From: idotj Date: Mon, 26 Feb 2024 13:50:32 +0100 Subject: [PATCH] Release v4.2.0 --- CHANGELOG | 8 + CONTRIBUTING.md | 84 + LICENSE | 2 +- README.md | 560 ++++--- dist/mastodon-timeline.d.ts | 1 + dist/mastodon-timeline.esm.js | 8 + dist/mastodon-timeline.min.js | 7 - dist/mastodon-timeline.umd.js | 8 + docker-compose.yml | 51 +- examples/hashtag-timeline.html | 8 +- examples/local-timeline-module.html | 132 ++ examples/local-timeline.html | 10 +- examples/multiple-timelines.html | 16 +- examples/profile-timeline.html | 8 +- examples/theme-timeline.html | 12 +- package-lock.json | 575 +++++++ package.json | 47 +- rollup.config.js | 20 + src/mastodon-timeline.css | 4 +- src/mastodon-timeline.js | 2238 ++++++++++++++------------- 20 files changed, 2360 insertions(+), 1439 deletions(-) create mode 100644 CONTRIBUTING.md create mode 100644 dist/mastodon-timeline.d.ts create mode 100644 dist/mastodon-timeline.esm.js delete mode 100644 dist/mastodon-timeline.min.js create mode 100644 dist/mastodon-timeline.umd.js create mode 100644 examples/local-timeline-module.html create mode 100644 package-lock.json create mode 100644 rollup.config.js diff --git a/CHANGELOG b/CHANGELOG index 2fbbb1e..fd1c79f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,11 @@ +v4.2.0 - 26/02/2024 +- Changed project name: mastodon-embed-feed-timeline -> mastodon-embed-timeline +- Improved DOM load for module purposes +- New build script with Rollup.js +- Added CDN and NPM install steps +- Added Contributing documentation +- Added example for JS module case + v4.0.4 - 20/02/2024 - Improve alignment for refresh button diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..6de3944 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,84 @@ +# Contributing to Mastodon embed timeline + +Thanks for your interest in contributing. Any feature and improvement from the community to make this project better is always welcome. + +## How to Contribute + +### Reporting Issues + +If you find any bugs, issues, or have suggestions, please [create a new issue](https://gitlab.com/idotj/mastodon-embed-timeline/-/issues/new) and provide detailed information about the problem or feature. + +### Code Contributions + +1. Fork the repository on GitLab. +2. Create a new branch from the `main` branch for your changes. +3. Make your modifications and ensure that your code follows the coding standards. +4. Compile and test your changes thoroughly. +5. Submit a pull request to the `main` branch with a clear title and description. + +## Getting Started + +### Setup your environment + +- Choose your favorite IDE and check that the configuration matches the `.editorconfig` setup to respect the same coding styles. + By default, this project is developed using [VScode](https://code.visualstudio.com/) with the plugins [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) and [SonarLint](https://marketplace.visualstudio.com/items?itemName=SonarSource.sonarlint-vscode). + +- Get [Git](https://git-scm.com/downloads) if you don't have it in your computer and after installation clone the repository by typing: + + ```terminal + git clone https://gitlab.com/idotj/mastodon-embed-timeline.git + ``` + +- Install [Node.js](https://nodejs.org/en) if you don't have it and then go into the project folder `mastodon-embed-timeline/` and enter: + + ```terminal + npm i + ``` + +- After all the packages are installed, do a check to see that it compiles as expected typing the following script to run a build: + + ```terminal + npm run build + ``` + +- All set, time to code! + +### Testing + +Ensure that your changes do not break existing functionality. If applicable, provide tests for new features or bug fixes. + +The example pages located in the folder `examples/` can be a good reference to test the changes made. + +If you need to emulate a server for your local development/testing, here are some options: + +- Install a static HTTP server via npm: + + ```terminal + npm install --global http-server + ``` + + After installation, run the command: + + ```terminal + http-server ./ + ``` + + Now you can open your browser and navigate to any of the HTML examples. For example to open a Local timeline, your default url will be: + [http://localhost:8080/examples/local-timeline.html](http://localhost:8080/examples/local-timeline.html) + +- Install [Docker compose](https://docs.docker.com/compose/install/) in your computer and run the following command: + + ```terminal + docker compose up + ``` + + Now open your browser and entering the following url you will land in the Local timeline page: + [http://localhost:8080/examples/local-timeline.html](http://localhost:8080/examples/local-timeline.html) + +## Code Review Process + +All contributions will go through a code review process. Be prepared to address feedback and make necessary changes to your code. + +## License + +By contributing to this project, you agree that your contributions will be licensed under the GNU Affero General Public License v3.0. diff --git a/LICENSE b/LICENSE index 918f236..b87bb1e 100644 --- a/LICENSE +++ b/LICENSE @@ -629,7 +629,7 @@ to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. - Mastodon embed feed timeline + Mastodon embed timeline Copyright (C) 2021 i.j This program is free software: you can redistribute it and/or modify diff --git a/README.md b/README.md index 31faa73..f7495d1 100644 --- a/README.md +++ b/README.md @@ -1,257 +1,303 @@ -# 🐘 Mastodon embed timeline (new v4) - -![Mastodon timeline widget screenshot](screenshot-light-dark.jpg "Mastodon timeline widget screenshot") - -Embed a mastodon feed timeline in your page, only with a CSS and JS file. - -Demo running: - - -## 📋 Table of contents - -- [**Installation**](#installation) -- [**Setup**](#setup) - - [Initialize](#initialize) - - [Local timeline](#local-timeline) - - [Profile timeline](#profile-timeline) - - [Hashtag timeline](#hashtag-timeline) - - [Customize](#customize) - - [API](#api) - - [Examples](#examples) - -## Installation - -Ready-to-use compiled and minified files to easily start. - -- Download into your project the following files: - - `dist/mastodon-timeline.min.css` - - `dist/mastodon-timeline.min.js` - -Now call the CSS and JS files in your HTML page using the `` and ` - - - -``` - -## Setup - -### Initialize - -The first step to get your timeline up is to add the following HTML structure in your page: - -```html -
-
-
-
-
-``` - -Then after that you can initialize the script by running: - -```js -const myTimeline = new MastodonTimeline(); -``` - -By default it will show a timeline with 20 posts from the instance [mastodon.social](https://mastodon.social/public/local) - -ℹī¸ If you are trying to initialize the script before `mastodon-timeline.min.js` is loaded, you will probably get such an error: "_MastodonTimeline is not defined_". To fix that initialize the script by running: - -```js -window.addEventListener("load", () => { - const myTimeline = new MastodonTimeline(); -}); -``` - -The next step is to configure the options/values of your timeline according to the type you need. There are three types, **Local**, **Profile** and **Hashtag**: - -#### Local timeline - -Add the following option/value when initializing the timeline: - -```js -const myTimeline = new MastodonTimeline({ - instanceUrl: "https://mastodon.online", -}); -``` - -It will show a timeline with posts from the instance [mastodon.online](https://mastodon.online/public/local) - -#### Profile timeline - -Add the following options/values when initializing the timeline: - -```js -const myTimeline = new MastodonTimeline({ - instanceUrl: "https://mastodon.online", - timelineType: "profile", - userId: "000180745", - profileName: "@idotj", -}); -``` - -It will show a timeline with posts from my Mastodon profile [@idotj](https://mastodon.online/@idotj) - -ℹī¸ If you don't know your `userId` you have two ways to get it: - -- Copy the url below and paste it in a new tab. Remember to replace the words `INSTANCE` and `USERNAME` with your current values in the url: - - The first value you see in the list is your `id` number. - -- Click on the link below and put your `@USERNAME` and `@INSTANCE` in the input field: - [https://prouser123.me/mastodon-userid-lookup/](https://prouser123.me/mastodon-userid-lookup/) - -#### Hashtag timeline - -Add the following options/values when initializing the timeline: - -```js -const myTimeline = new MastodonTimeline({ - instanceUrl: "https://mastodon.online", - timelineType: "hashtag", - hashtagName: "fediverse", -}); -``` - -It will show a timeline with posts containing the hashtag [#fediverse](https://mastodon.online/tags/fediverse) - -### Customize - -You can pass more options/values to personalize your timeline. Here you have all the available options: - -```js - // Id of the
containing the timeline - mtContainerId: "mt-container", - - // Mastodon instance Url (including https://) - instanceUrl: "https://mastodon.social", - - // Choose type of posts to show in the timeline: 'local', 'profile', 'hashtag'. Default: local - timelineType: "local", - - // Your user ID number on Mastodon instance. Leave it empty if you didn't choose 'profile' as type of timeline - userId: "", - - // Your user name on Mastodon instance (including the @ symbol at the beginning). Leave it empty if you didn't choose 'profile' as type of timeline - profileName: "", - - // The name of the hashtag (not including the # symbol). Leave it empty if you didn't choose 'hashtag' as type of timeline - hashtagName: "", - - // Class name for the loading spinner (also used in CSS file) - spinnerClass: "mt-loading-spinner", - - // Preferred color theme: 'light', 'dark' or 'auto'. Default: auto - defaultTheme: "auto", - - // Maximum number of posts to request to the server. Default: 20 - maxNbPostFetch: "20", - - // Maximum number of posts to show in the timeline. Default: 20 - maxNbPostShow: "20", - - // Hide unlisted posts. Default: don't hide - hideUnlisted: false, - - // Hide boosted posts. Default: don't hide - hideReblog: false, - - // Hide replies posts. Default: don't hide - hideReplies: false, - - // Hide video image preview and load video player instead. Default: don't hide - hideVideoPreview: false, - - // Hide preview card if post contains a link, photo or video from a Url. Default: don't hide - hidePreviewLink: false, - - // Hide custom emojis available on the server. Default: don't hide - hideEmojos: false, - - // Converts Markdown symbol ">" at the beginning of a paragraph into a blockquote HTML tag. Default: don't apply - markdownBlockquote: false, - - // Hide replies, boosts and favourites posts counter. Default: don't hide - hideCounterBar: false, - - // 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 - btnShowMore: "SHOW MORE", - btnShowLess: "SHOW LESS", - - // Customize the text of the button used for showing sensitive/spolier media content - btnShowContent: "SHOW CONTENT", - - // Customize the text of the button pointing to the Mastodon page placed at the end of the timeline. Leave the value empty to hide it - btnSeeMore: "See more posts at Mastodon", - - // Customize the text of the button reloading the list of posts placed at the end of the timeline. Leave the value empty to hide it - btnReload: "Refresh", - -``` - -### API - -| Function | Description | -| --- | --- | -| `mtColorTheme(themeType)` | Apply a theme color. `themeType` accepts only two values: `'light'` or `'dark'` | -| `mtUpdate()` | Reload the timeline by fetching the lastest posts | - -### Examples - -The folder `examples/` contains several demos in HTML to play with. Download the full project and open each HTML file in your favorite browser. - -Also, you have a Docker file to perform your tests if needed. Simply run: - -```terminal -docker compose up -``` - -## 🌐 Browser support - -Mastodon embed timeline is supported on the latest versions of the following browsers: - -- Chrome -- Firefox -- Edge -- Safari -- Brave -- Opera - -## 🚀 Improve me - -Feel free to add your features and improvements. - -## ⚖ī¸ License - -GNU Affero General Public License v3.0 - -## đŸ’Ŧ FAQ - -Check the [closed issues](https://gitlab.com/idotj/mastodon-embed-feed-timeline/-/issues/?sort=created_date&state=closed&first_page_size=20), you might find your question there. - -If nothing matches with your problem, check the [open issues](https://gitlab.com/idotj/mastodon-embed-feed-timeline/-/issues/?sort=created_date&state=opened&first_page_size=20) or feel free to create a new one. - -Looking for a previous version of Mastodon embed timeline? -Check on the tags list to see all the released versions: [Tags version history](https://gitlab.com/idotj/mastodon-embed-feed-timeline/-/tags) +# 🐘 Mastodon embed timeline (new v4.2) + +![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. + +Demo running: + + +## 📋 Table of contents + +- [🐘 Mastodon embed timeline (new v4.2)](#-mastodon-embed-timeline-new-v42) + - [📋 Table of contents](#-table-of-contents) + - [Installation](#installation) + - [Download](#download) + - [CDN](#cdn) + - [Package manager](#package-manager) + - [Setup](#setup) + - [Initialize](#initialize) + - [Local timeline](#local-timeline) + - [Profile timeline](#profile-timeline) + - [Hashtag timeline](#hashtag-timeline) + - [Customize](#customize) + - [API](#api) + - [Examples](#examples) + - [🌐 Browser support](#-browser-support) + - [🚀 Improve me](#-improve-me) + - [⚖ī¸ License](#ī¸-license) + - [đŸ’Ŧ FAQ](#-faq) + +## Installation + +### Download + +Ready-to-use compiled and minified files to easily start. + +- Download into your project the following files: + - `dist/mastodon-timeline.min.css` + - `dist/mastodon-timeline.umd.js` + +Now call the CSS and JS files in your HTML page using the `` and ` + + + +``` + +### CDN + +This option allows you to get started quickly without the need to upload any files into your server. + +```html + + + +``` + +### Package manager + +Install your timeline using npm or yarn: + +```terminal +npm install @idot/mastodon-embed-timeline +``` + +or + +```terminal +yarn add @idot/mastodon-embed-timeline +``` + +After installation, you can import the widget as follows: + +```js +import * as MastodonTimeline from "@idotj/mastodon-embed-timeline"; +``` + +Make sure to import also the `@idotj/mastodon-embed-timeline/dist/mastodon-timeline.min.css` file in your project. + +## Setup + +### Initialize + +The first step to get your timeline up is to add the following HTML structure in your page: + +```html +
+
+
+
+
+``` + +Then after that you can initialize the script by running: + +```js +const myTimeline = new MastodonTimeline.Init(); +``` + +By default it will show a timeline with 20 posts from the instance [mastodon.social](https://mastodon.social/public/local) + +ℹī¸ If you are trying to initialize the script before `mastodon-timeline.umd.js` is loaded, you will probably get such an error in the console: "_MastodonTimeline is not defined_". To fix that initialize the script by running: + +```js +window.addEventListener("load", () => { + const myTimeline = new MastodonTimeline.Init(); +}); +``` + +The next step is to configure the options/values of your timeline according to the type you need. There are three types, **Local**, **Profile** and **Hashtag**: + +#### Local timeline + +Add the following option/value when initializing the timeline: + +```js +const myTimeline = new MastodonTimeline.Init({ + instanceUrl: "https://mastodon.online", +}); +``` + +It will show a timeline with posts from the instance [mastodon.online](https://mastodon.online/public/local) + +#### Profile timeline + +Add the following options/values when initializing the timeline: + +```js +const myTimeline = new MastodonTimeline.Init({ + instanceUrl: "https://mastodon.online", + timelineType: "profile", + userId: "000180745", + profileName: "@idotj", +}); +``` + +It will show a timeline with posts from my Mastodon profile [@idotj](https://mastodon.online/@idotj) + +ℹī¸ If you don't know your `userId` you have two ways to get it: + +- Copy the url below and paste it in a new tab. Remember to replace the words `INSTANCE` and `USERNAME` with your current values in the url: + + The first value you see in the list is your `id` number. + +- Click on the link below and put your `@USERNAME` and `@INSTANCE` in the input field: + [https://prouser123.me/mastodon-userid-lookup/](https://prouser123.me/mastodon-userid-lookup/) + +#### Hashtag timeline + +Add the following options/values when initializing the timeline: + +```js +const myTimeline = new MastodonTimeline.Init({ + instanceUrl: "https://mastodon.online", + timelineType: "hashtag", + hashtagName: "fediverse", +}); +``` + +It will show a timeline with posts containing the hashtag [#fediverse](https://mastodon.online/tags/fediverse) + +### Customize + +You can pass more options/values to personalize your timeline. Here you have all the available options: + +```js + // Id of the
containing the timeline + mtContainerId: "mt-container", + + // Mastodon instance Url (including https://) + instanceUrl: "https://mastodon.social", + + // Choose type of posts to show in the timeline: 'local', 'profile', 'hashtag'. Default: local + timelineType: "local", + + // Your user ID number on Mastodon instance. Leave it empty if you didn't choose 'profile' as type of timeline + userId: "", + + // Your user name on Mastodon instance (including the @ symbol at the beginning). Leave it empty if you didn't choose 'profile' as type of timeline + profileName: "", + + // The name of the hashtag (not including the # symbol). Leave it empty if you didn't choose 'hashtag' as type of timeline + hashtagName: "", + + // Class name for the loading spinner (also used in CSS file) + spinnerClass: "mt-loading-spinner", + + // Preferred color theme: 'light', 'dark' or 'auto'. Default: auto + defaultTheme: "auto", + + // Maximum number of posts to request to the server. Default: 20 + maxNbPostFetch: "20", + + // Maximum number of posts to show in the timeline. Default: 20 + maxNbPostShow: "20", + + // Hide unlisted posts. Default: don't hide + hideUnlisted: false, + + // Hide boosted posts. Default: don't hide + hideReblog: false, + + // Hide replies posts. Default: don't hide + hideReplies: false, + + // Hide video image preview and load video player instead. Default: don't hide + hideVideoPreview: false, + + // Hide preview card if post contains a link, photo or video from a Url. Default: don't hide + hidePreviewLink: false, + + // Hide custom emojis available on the server. Default: don't hide + hideEmojos: false, + + // Converts Markdown symbol ">" at the beginning of a paragraph into a blockquote HTML tag. Default: don't apply + markdownBlockquote: false, + + // Hide replies, boosts and favourites posts counter. Default: don't hide + hideCounterBar: false, + + // 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 + btnShowMore: "SHOW MORE", + btnShowLess: "SHOW LESS", + + // Customize the text of the button used for showing sensitive/spolier media content + btnShowContent: "SHOW CONTENT", + + // Customize the text of the button pointing to the Mastodon page placed at the end of the timeline. Leave the value empty to hide it + btnSeeMore: "See more posts at Mastodon", + + // Customize the text of the button reloading the list of posts placed at the end of the timeline. Leave the value empty to hide it + btnReload: "Refresh", + + // Keep searching for the main
container before building the timeline. Useful in some cases where extra time is needed to render the page. Default: false + insistSearchContainer: false, + + // Defines the maximum time to continue searching for the main
container. Default: 3 seconds + insistSearchContainerTime: "3000", + +``` + +### API + +| Function | Description | +| ------------------------- | ------------------------------------------------------------------------------- | +| `mtColorTheme(themeType)` | Apply a theme color. `themeType` accepts only two values: `'light'` or `'dark'` | +| `mtUpdate()` | Reload the timeline by fetching the lastest posts | + +### Examples + +The folder `examples/` contains several demos in HTML to play with. Download the full project and open each HTML file in your favorite browser. + +Also, you have other alternatives to run these examples locally. Consult the document [CONTRIBUTING.md](https://gitlab.com/idotj/mastodon-embed-timeline/-/blob/master/CONTRIBUTING.md#testing) to use other options like Docker or Http-server. + +## 🌐 Browser support + +Mastodon embed timeline is supported on the latest versions of the following browsers: + +- Chrome +- Firefox +- Edge +- Safari +- Brave +- Opera + +## 🚀 Improve me + +Feel free to add your features and improvements. +Take a look at the [CONTRIBUTING.md](https://gitlab.com/idotj/mastodon-embed-timeline/-/blob/master/CONTRIBUTING.md) document to learn more about how to build and collaborate on the project. + +## ⚖ī¸ License + +GNU Affero General Public License v3.0 + +## đŸ’Ŧ FAQ + +Check the [closed issues](https://gitlab.com/idotj/mastodon-embed-timeline/-/issues/?sort=created_date&state=closed&first_page_size=20), you might find your question there. + +If nothing matches with your problem, check the [open issues](https://gitlab.com/idotj/mastodon-embed-timeline/-/issues/?sort=created_date&state=opened&first_page_size=20) or feel free to create a new one. + +Looking for a previous version of Mastodon embed timeline? +Check on the tags list to see all the released versions: [Tags history](https://gitlab.com/idotj/mastodon-embed-timeline/-/tags) diff --git a/dist/mastodon-timeline.d.ts b/dist/mastodon-timeline.d.ts new file mode 100644 index 0000000..342df31 --- /dev/null +++ b/dist/mastodon-timeline.d.ts @@ -0,0 +1 @@ +declare module '@idotj/mastodon-embed-timeline'; diff --git a/dist/mastodon-timeline.esm.js b/dist/mastodon-timeline.esm.js new file mode 100644 index 0000000..0570cf8 --- /dev/null +++ b/dist/mastodon-timeline.esm.js @@ -0,0 +1,8 @@ +/** + * Mastodon embed timeline + * @author idotj + * @version 4.2.0 + * @url https://gitlab.com/idotj/mastodon-embed-timeline + * @license GNU AGPLv3 + */ +class t{constructor(t={}){this.defaultSettings={mtContainerId:"mt-container",instanceUrl:"https://mastodon.social",timelineType:"local",userId:"",profileName:"",hashtagName:"",spinnerClass:"mt-loading-spinner",defaultTheme:"auto",maxNbPostFetch:"20",maxNbPostShow:"20",hideUnlisted:!1,hideReblog:!1,hideReplies:!1,hideVideoPreview:!1,hidePreviewLink:!1,hideEmojos:!1,markdownBlockquote:!1,hideCounterBar:!1,txtMaxLines:"0",btnShowMore:"SHOW MORE",btnShowLess:"SHOW LESS",btnShowContent:"SHOW CONTENT",btnSeeMore:"See more posts at Mastodon",btnReload:"Refresh",insistSearchContainer:!1,insistSearchContainerTime:"3000"},this.mtSettings={...this.defaultSettings,...t},this.mtContainerNode="",this.mtBodyNode="",this.fetchedData={},this.#t((()=>{this.#e()}))}#t(t){"undefined"!=typeof document&&"complete"===document.readyState?t():"undefined"!=typeof document&&"complete"!==document.readyState&&document.addEventListener("DOMContentLoaded",t())}#e(){const t=()=>{this.mtContainerNode=document.getElementById(this.mtSettings.mtContainerId),this.mtBodyNode=this.mtContainerNode.getElementsByClassName("mt-body")[0],this.#s(),this.#i("newTimeline")};if(this.mtSettings.insistSearchContainer){const e=performance.now(),s=()=>{if(document.getElementById(this.mtSettings.mtContainerId))t();else{performance.now()-e container with id: "${this.mtSettings.mtContainerId}" after several attempts for ${this.mtSettings.insistSearchContainerTime/1e3} seconds`)}};s()}else document.getElementById(this.mtSettings.mtContainerId)?t():console.error(`Impossible to find the
container with id: "${this.mtSettings.mtContainerId}". Please try to add the option 'insistSearchContainer: true' when initializing the script`)}mtUpdate(){this.#t((()=>{this.mtBodyNode.replaceChildren(),this.mtBodyNode.insertAdjacentHTML("afterbegin",'
'),this.#i("updateTimeline")}))}mtColorTheme(t){this.#t((()=>{this.mtContainerNode.setAttribute("data-theme",t)}))}#s(){if("auto"===this.mtSettings.defaultTheme){let t=window.matchMedia("(prefers-color-scheme: dark)");t.matches?this.mtColorTheme("dark"):this.mtColorTheme("light"),t.addEventListener("change",(t=>{t.matches?this.mtColorTheme("dark"):this.mtColorTheme("light")}))}else this.mtColorTheme(this.mtSettings.defaultTheme)}#a(){return new Promise(((t,e)=>{let s={};this.mtSettings.instanceUrl?"profile"===this.mtSettings.timelineType?this.mtSettings.userId?s.timeline=`${this.mtSettings.instanceUrl}/api/v1/accounts/${this.mtSettings.userId}/statuses?limit=${this.mtSettings.maxNbPostFetch}`:this.#o("Please check your userId value","⚠ī¸"):"hashtag"===this.mtSettings.timelineType?this.mtSettings.hashtagName?s.timeline=`${this.mtSettings.instanceUrl}/api/v1/timelines/tag/${this.mtSettings.hashtagName}?limit=${this.mtSettings.maxNbPostFetch}`:this.#o("Please check your hashtagName value","⚠ī¸"):"local"===this.mtSettings.timelineType?s.timeline=`${this.mtSettings.instanceUrl}/api/v1/timelines/public?local=true&limit=${this.mtSettings.maxNbPostFetch}`:this.#o("Please check your timelineType value","⚠ī¸"):this.#o("Please check your instanceUrl value","⚠ī¸"),this.mtSettings.hideEmojos||(s.emojos=this.mtSettings.instanceUrl+"/api/v1/custom_emojis");const i=Object.entries(s).map((([t,s])=>async function(t){const e=await fetch(t);if(!e.ok)throw new Error("Failed to fetch the following Url:
"+t+"
Error status: "+e.status+"
Error message: "+e.statusText);return await e.json()}(s).then((e=>({[t]:e}))).catch((i=>(e(new Error("Something went wrong fetching data from: "+s)),this.#o(i.message),{[t]:[]})))));Promise.all(i).then((e=>{this.mtSettings.fetchedData=e.reduce(((t,e)=>({...t,...e})),{}),t()}))}))}async#i(t){await this.#a(),this.mtBodyNode.replaceChildren();let e=0;for(let t in this.mtSettings.fetchedData.timeline)("public"==this.mtSettings.fetchedData.timeline[t].visibility||!this.mtSettings.hideUnlisted&&"unlisted"==this.mtSettings.fetchedData.timeline[t].visibility)&&(this.mtSettings.hideReblog&&this.mtSettings.fetchedData.timeline[t].reblog||this.mtSettings.hideReplies&&this.mtSettings.fetchedData.timeline[t].in_reply_to_id||eThis may be due to an incorrect configuration in the parameters or to filters applied (to hide certains type of posts)";this.#o(t,"📭")}else"newTimeline"===t?(this.#r(),this.#l(),this.#m()):"updateTimeline"===t?this.#r():this.#o("The function buildTimeline() was expecting a param")}#n(t,e){this.mtBodyNode.insertAdjacentHTML("beforeend",this.#d(t,e))}#d(t,e){let s,i,a,o,n,r,l,m,d;t.reblog?(o=t.reblog.url,s='
'+this.#c(t.reblog.account.username)+' avatar
'+this.#c(t.account.username)+' avatar
',a=t.reblog.account.display_name?t.reblog.account.display_name:t.reblog.account.username,this.mtSettings.hideEmojos||(a=this.#h(a,this.mtSettings.fetchedData.emojos)),i='
'+a+' account
',n=t.reblog.created_at,d=t.reblog.replies_count,m=t.reblog.reblogs_count,l=t.reblog.favourites_count):(o=t.url,s='
'+this.#c(t.account.username)+' avatar
',a=t.account.display_name?t.account.display_name:t.account.username,this.mtSettings.hideEmojos||(a=this.#h(a,this.mtSettings.fetchedData.emojos)),i='
'+a+' account
',n=t.created_at,d=t.replies_count,m=t.reblogs_count,l=t.favourites_count),r=this.#p(n);const c='
";let h="";"0"!==this.mtSettings.txtMaxLines&&(h=" truncate",this.mtBodyNode.parentNode.style.setProperty("--mt-txt-max-lines",this.mtSettings.txtMaxLines));let p="";p=""!==t.spoiler_text?'
'+t.spoiler_text+'
'+this.#g(t.content)+"
":t.reblog&&""!==t.reblog.content&&""!==t.reblog.spoiler_text?'
'+t.reblog.spoiler_text+'
'+this.#g(t.reblog.content)+"
":t.reblog&&""!==t.reblog.content&&""===t.reblog.spoiler_text?'
'+this.#g(t.reblog.content)+"
":'
'+this.#g(t.content)+"
";let g=[];if(t.media_attachments.length>0)for(let e in t.media_attachments)g.push(this.#u(t.media_attachments[e],t.sensitive));if(t.reblog&&t.reblog.media_attachments.length>0)for(let e in t.reblog.media_attachments)g.push(this.#u(t.reblog.media_attachments[e],t.reblog.sensitive));let u="";!this.mtSettings.hidePreviewLink&&t.card&&(u=this.#v(t.card));let v="";if(t.poll){let e="";for(let s in t.poll.options)e+="
  • "+t.poll.options[s].title+"
  • ";v='
      '+e+"
    "}let b="";if(!this.mtSettings.hideCounterBar){b='
    '+('
    '+d+"
    ")+('
    '+m+"
    ")+('
    '+l+"
    ")+"
    "}return'
    '+s+i+c+"
    "+p+g.join("")+u+v+b+"
    "}#g(t){let e=t;return e=this.#b(e),this.mtSettings.hideEmojos||(e=this.#h(e,this.mtSettings.fetchedData.emojos)),this.mtSettings.markdownBlockquote&&(e=this.#S(e,"

    >","

    ","

    ","

    ")),e}#b(t){let e=t.replaceAll('rel="tag"','rel="tag" target="_blank"');return e=e.replaceAll('class="u-url mention"','class="u-url mention" target="_blank"'),e}#S(t,e,s,i,a){if(t.includes(e)){const o=new RegExp(e+"(.*?)"+s,"gi");return t.replace(o,i+"$1"+a)}return t}#c(t){return(t??"").replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'")}#h(t,e){if(t.includes(":")){for(const s of e){const e=new RegExp(`\\:${s.shortcode}\\:`,"g");t=t.replace(e,`Emoji ${s.shortcode}`)}return t}return t}#p(t){const e=new Date(t);return["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"][e.getMonth()]+" "+e.getDate()+", "+e.getFullYear()}#u(t,e){const s=e||!1,i=t.type;let a="";return"image"===i&&(a='
    '+(s?'":"")+''+(t.description?this.#c(t.description):
    '),"audio"===i&&(a=t.preview_url?'
    '+(s?'":"")+''+(t.description?this.#c(t.description):
    ':'
    '+(s?'":"")+'
    '),"video"!==i&&"gifv"!==i||(a=this.mtSettings.hideVideoPreview?'
    '+(s?'":"")+'
    ':'
    '+(s?'":"")+''+(t.description?this.#c(t.description):
    '),a}#f(t){const e=t.target.closest("[data-video-url]"),s=e.dataset.videoUrl;e.replaceChildren(),e.innerHTML=''}#w(t){const e=t.target.nextSibling;"img"===e.localName||"audio"===e.localName||"video"===e.localName?(t.target.parentNode.classList.remove("mt-post-media-spoiler"),t.target.style.display="none"):(e.classList.contains("spoiler-txt-hidden")||e.classList.contains("spoiler-txt-visible"))&&(t.target.textContent==this.mtSettings.btnShowMore?(e.classList.remove("spoiler-txt-hidden"),e.classList.add("spoiler-txt-visible"),t.target.setAttribute("aria-expanded","true"),t.target.textContent=this.mtSettings.btnShowLess):(e.classList.remove("spoiler-txt-visible"),e.classList.add("spoiler-txt-hidden"),t.target.setAttribute("aria-expanded","false"),t.target.textContent=this.mtSettings.btnShowMore))}#v(t){return''+(t.image?'
    '+this.#c(t.image_description)+'
    ':'
    📄
    ')+'
    '+(t.provider_name?''+this.#N(t.provider_name)+"":"")+''+t.title+""+(t.author_name?''+this.#N(t.author_name)+"":"")+"
    "}#N(t){return(new DOMParser).parseFromString(t,"text/html").body.textContent}#m(){if(this.mtSettings.btnSeeMore||this.mtSettings.btnReload){this.mtBodyNode.parentNode.insertAdjacentHTML("beforeend",'');const t=this.mtContainerNode.getElementsByClassName("mt-footer")[0];if(this.mtSettings.btnSeeMore){let e="";"profile"===this.mtSettings.timelineType?this.mtSettings.profileName?e=this.mtSettings.profileName:this.#o("Please check your profileName value","⚠ī¸"):"hashtag"===this.mtSettings.timelineType?e="tags/"+this.mtSettings.hashtagName:"local"===this.mtSettings.timelineType&&(e="public/local");const s=''+this.mtSettings.btnSeeMore+"";t.insertAdjacentHTML("beforeend",s)}if(this.mtSettings.btnReload){const e='";t.insertAdjacentHTML("beforeend",e);this.mtContainerNode.getElementsByClassName("btn-refresh")[0].addEventListener("click",(()=>{this.mtUpdate()}))}}}#l(){this.mtBodyNode.addEventListener("click",(t=>{("article"==t.target.localName||"article"==t.target.offsetParent?.localName||"img"==t.target.localName&&!t.target.parentNode.getAttribute("data-video-url"))&&this.#y(t),"button"==t.target.localName&&t.target.classList.contains("mt-btn-spoiler")&&this.#w(t),("mt-post-media-play-icon"==t.target.className||"svg"==t.target.localName&&"mt-post-media-play-icon"==t.target.parentNode.className||"path"==t.target.localName&&"mt-post-media-play-icon"==t.target.parentNode.parentNode.className||"img"==t.target.localName&&t.target.parentNode.getAttribute("data-video-url"))&&this.#f(t)})),this.mtBodyNode.addEventListener("keydown",(t=>{"Enter"===t.key&&"article"==t.target.localName&&this.#y(t)}))}#y(t){const e=t.target.closest(".mt-post").dataset.location;"a"!==t.target.localName&&"span"!==t.target.localName&&"button"!==t.target.localName&&"time"!==t.target.localName&&"mt-post-preview-noImage"!==t.target.className&&"mt-post-avatar-image-big"!==t.target.parentNode.className&&"mt-post-avatar-image-small"!==t.target.parentNode.className&&"mt-post-preview-image"!==t.target.parentNode.className&&"mt-post-preview"!==t.target.parentNode.className&&e&&window.open(e,"_blank","noopener")}#r(){const t=e=>{e.target.parentNode.classList.remove(this.mtSettings.spinnerClass),e.target.removeEventListener("load",t),e.target.removeEventListener("error",t)};this.mtBodyNode.querySelectorAll(`.${this.mtSettings.spinnerClass} > img`).forEach((e=>{e.addEventListener("load",t),e.addEventListener("error",t)}))}#o(t,e){const s=e||"❌";throw this.mtBodyNode.innerHTML='
    '+s+'
    Oops, something\'s happened:
    '+t+"
    ",this.mtBodyNode.setAttribute("role","none"),new Error("Stopping the script due to an error building the timeline.")}}export{t as Init}; diff --git a/dist/mastodon-timeline.min.js b/dist/mastodon-timeline.min.js deleted file mode 100644 index a7edb76..0000000 --- a/dist/mastodon-timeline.min.js +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Mastodon embed feed timeline - * @author idotj - * @version 4.0.4 - * @url https://gitlab.com/idotj/mastodon-embed-feed-timeline - * @license GNU AGPLv3 -*/ "use strict";class MastodonTimeline{constructor(t={}){this.defaultSettings={mtContainerId:"mt-container",instanceUrl:"https://mastodon.social",timelineType:"local",userId:"",profileName:"",hashtagName:"",spinnerClass:"mt-loading-spinner",defaultTheme:"auto",maxNbPostFetch:"20",maxNbPostShow:"20",hideUnlisted:!1,hideReblog:!1,hideReplies:!1,hideVideoPreview:!1,hidePreviewLink:!1,hideEmojos:!1,markdownBlockquote:!1,hideCounterBar:!1,txtMaxLines:"0",btnShowMore:"SHOW MORE",btnShowLess:"SHOW LESS",btnShowContent:"SHOW CONTENT",btnSeeMore:"See more posts at Mastodon",btnReload:"Refresh"},this.mtSettings={...this.defaultSettings,...t},this.mtContainerNode="",this.mtBodyNode="",this.fetchedData={},this.mtInit()}onDOMContentLoaded(t){"undefined"!=typeof document&&("complete"===document.readyState||"interactive"===document.readyState)?t():"undefined"!=typeof document&&("complete"!==document.readyState||"interactive"!==document.readyState)&&document.addEventListener("DOMContentLoaded",t)}mtInit(){this.onDOMContentLoaded(()=>{this.mtContainerNode=document.getElementById(this.mtSettings.mtContainerId),this.mtBodyNode=this.mtContainerNode.getElementsByClassName("mt-body")[0],this.#a(),this.#b("newTimeline")})}mtUpdate(){this.onDOMContentLoaded(()=>{this.mtBodyNode.replaceChildren(),this.mtBodyNode.insertAdjacentHTML("afterbegin",'
    '),this.#b("updateTimeline")})}mtColorTheme(t){this.onDOMContentLoaded(()=>{this.mtContainerNode.setAttribute("data-theme",t)})}#a(){if("auto"===this.mtSettings.defaultTheme){let t=window.matchMedia("(prefers-color-scheme: dark)");t.matches?this.mtColorTheme("dark"):this.mtColorTheme("light"),t.addEventListener("change",t=>{t.matches?this.mtColorTheme("dark"):this.mtColorTheme("light")})}else this.mtColorTheme(this.mtSettings.defaultTheme)}#c(){return new Promise((t,e)=>{async function s(t){let e=await fetch(t);if(!e.ok)throw Error("Failed to fetch the following Url:
    "+t+"
    Error status: "+e.status+"
    Error message: "+e.statusText);let s=await e.json();return s}let i={};this.mtSettings.instanceUrl?"profile"===this.mtSettings.timelineType?this.mtSettings.userId?i.timeline=`${this.mtSettings.instanceUrl}/api/v1/accounts/${this.mtSettings.userId}/statuses?limit=${this.mtSettings.maxNbPostFetch}`:this.#d("Please check your userId value","⚠ī¸"):"hashtag"===this.mtSettings.timelineType?this.mtSettings.hashtagName?i.timeline=`${this.mtSettings.instanceUrl}/api/v1/timelines/tag/${this.mtSettings.hashtagName}?limit=${this.mtSettings.maxNbPostFetch}`:this.#d("Please check your hashtagName value","⚠ī¸"):"local"===this.mtSettings.timelineType?i.timeline=`${this.mtSettings.instanceUrl}/api/v1/timelines/public?local=true&limit=${this.mtSettings.maxNbPostFetch}`:this.#d("Please check your timelineType value","⚠ī¸"):this.#d("Please check your instanceUrl value","⚠ī¸"),this.mtSettings.hideEmojos||(i.emojos=this.mtSettings.instanceUrl+"/api/v1/custom_emojis");let a=Object.entries(i).map(([t,i])=>s(i).then(e=>({[t]:e})).catch(s=>(e(Error("Something went wrong fetching data from: "+i)),this.#d(s.message),{[t]:[]})));Promise.all(a).then(e=>{this.mtSettings.fetchedData=e.reduce((t,e)=>({...t,...e}),{}),t()})})}async #b(e){await this.#c(),this.mtBodyNode.replaceChildren();let s=0;for(let i in this.mtSettings.fetchedData.timeline)("public"==this.mtSettings.fetchedData.timeline[i].visibility||!this.mtSettings.hideUnlisted&&"unlisted"==this.mtSettings.fetchedData.timeline[i].visibility)&&(this.mtSettings.hideReblog&&this.mtSettings.fetchedData.timeline[i].reblog||this.mtSettings.hideReplies&&this.mtSettings.fetchedData.timeline[i].in_reply_to_id||sThis may be due to an incorrect configuration in the parameters or to filters applied (to hide certains type of posts)";this.#d(a,"\uD83D\uDCED")}else"newTimeline"===e?(this.#f(),this.#g(),this.#h()):"updateTimeline"===e?this.#f():this.#d("The function buildTimeline() was expecting a param")}#e(o,n){this.mtBodyNode.insertAdjacentHTML("beforeend",this.#i(o,n))}#i(r,l){let m,d,h,c,p,g,u,v,b;r.reblog?(c=r.reblog.url,m='
    '+this.#j(r.reblog.account.username)+' avatar
    '+this.#j(r.account.username)+' avatar
    ',h=r.reblog.account.display_name?r.reblog.account.display_name:r.reblog.account.username,this.mtSettings.hideEmojos||(h=this.#k(h,this.mtSettings.fetchedData.emojos)),d='
    '+h+' account
    ',p=r.reblog.created_at,b=r.reblog.replies_count,v=r.reblog.reblogs_count,u=r.reblog.favourites_count):(c=r.url,m='
    '+this.#j(r.account.username)+' avatar
    ',h=r.account.display_name?r.account.display_name:r.account.username,this.mtSettings.hideEmojos||(h=this.#k(h,this.mtSettings.fetchedData.emojos)),d='
    '+h+' account
    ',p=r.created_at,b=r.replies_count,v=r.reblogs_count,u=r.favourites_count),g=this.#l(p);let S='
    ",$="";"0"!==this.mtSettings.txtMaxLines&&($=" truncate",this.mtBodyNode.parentNode.style.setProperty("--mt-txt-max-lines",this.mtSettings.txtMaxLines));let f="";f=""!==r.spoiler_text?'
    '+r.spoiler_text+'
    '+this.#m(r.content)+"
    ":r.reblog&&""!==r.reblog.content&&""!==r.reblog.spoiler_text?'
    '+r.reblog.spoiler_text+'
    '+this.#m(r.reblog.content)+"
    ":r.reblog&&""!==r.reblog.content&&""===r.reblog.spoiler_text?'
    '+this.#m(r.reblog.content)+"
    ":'
    '+this.#m(r.content)+"
    ";let _=[];if(r.media_attachments.length>0)for(let w in r.media_attachments)_.push(this.#n(r.media_attachments[w],r.sensitive));if(r.reblog&&r.reblog.media_attachments.length>0)for(let y in r.reblog.media_attachments)_.push(this.#n(r.reblog.media_attachments[y],r.reblog.sensitive));let N="";!this.mtSettings.hidePreviewLink&&r.card&&(N=this.#o(r.card));let x="";if(r.poll){let L="";for(let T in r.poll.options)L+="
  • "+r.poll.options[T].title+"
  • ";x='
      '+L+"
    "}let C="";if(!this.mtSettings.hideCounterBar){let M='
    '+b+"
    ",k='
    '+v+"
    ",E='
    '+u+"
    ";C='
    '+M+k+E+"
    "}let P='
    '+m+d+S+"
    "+f+_.join("")+N+x+C+"
    ";return P}#m(B){let H=B;return H=this.#p(H),this.mtSettings.hideEmojos||(H=this.#k(H,this.mtSettings.fetchedData.emojos)),this.mtSettings.markdownBlockquote&&(H=this.#q(H,"

    >","

    ","

    ","

    ")),H}#p(j){let A=j.replaceAll('rel="tag"','rel="tag" target="_blank"');return A.replaceAll('class="u-url mention"','class="u-url mention" target="_blank"')}#q(D,U,I,O,F){if(!D.includes(U))return D;{let R=RegExp(U+"(.*?)"+I,"gi");return D.replace(R,O+"$1"+F)}}#j(z){return(z??"").replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'")}#k(q,Z){if(!q.includes(":"))return q;for(let V of Z){let J=RegExp(`\\:${V.shortcode}\\:`,"g");q=q.replace(J,`Emoji ${V.shortcode}`)}return q}#l(W){let Y=new Date(W),G=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec",][Y.getMonth()]+" "+Y.getDate()+", "+Y.getFullYear();return G}#n(K,Q){let X=Q||!1,tt=K.type,te="";return"image"===tt&&(te='
    '+(X?'":"")+''+(K.description?this.#j(K.description):
    '),"audio"===tt&&(te=K.preview_url?'
    '+(X?'":"")+''+(K.description?this.#j(K.description):
    ':'
    '+(X?'":"")+'
    '),("video"===tt||"gifv"===tt)&&(te=this.mtSettings.hideVideoPreview?'
    '+(X?'":"")+'
    ':'
    '+(X?'":"")+''+(K.description?this.#j(K.description):
    '),te}#r(ts){let ti=ts.target.closest("[data-video-url]"),ta=ti.dataset.videoUrl;ti.replaceChildren(),ti.innerHTML=''}#s(to){let tn=to.target.nextSibling;"img"===tn.localName||"audio"===tn.localName||"video"===tn.localName?(to.target.parentNode.classList.remove("mt-post-media-spoiler"),to.target.style.display="none"):(tn.classList.contains("spoiler-txt-hidden")||tn.classList.contains("spoiler-txt-visible"))&&(to.target.textContent==this.mtSettings.btnShowMore?(tn.classList.remove("spoiler-txt-hidden"),tn.classList.add("spoiler-txt-visible"),to.target.setAttribute("aria-expanded","true"),to.target.textContent=this.mtSettings.btnShowLess):(tn.classList.remove("spoiler-txt-visible"),tn.classList.add("spoiler-txt-hidden"),to.target.setAttribute("aria-expanded","false"),to.target.textContent=this.mtSettings.btnShowMore))}#o(tr){let tl=''+(tr.image?'
    '+this.#j(tr.image_description)+'
    ':'
    \uD83D\uDCC4
    ')+'
    '+(tr.provider_name?''+this.#t(tr.provider_name)+"":"")+''+tr.title+""+(tr.author_name?''+this.#t(tr.author_name)+"":"")+"
    ";return tl}#t(tm){let td=new DOMParser,th=td.parseFromString(tm,"text/html");return th.body.textContent}#h(){if(this.mtSettings.btnSeeMore||this.mtSettings.btnReload){this.mtBodyNode.parentNode.insertAdjacentHTML("beforeend",'');let tc=this.mtContainerNode.getElementsByClassName("mt-footer")[0];if(this.mtSettings.btnSeeMore){let tp="";"profile"===this.mtSettings.timelineType?this.mtSettings.profileName?tp=this.mtSettings.profileName:this.#d("Please check your profileName value","⚠ī¸"):"hashtag"===this.mtSettings.timelineType?tp="tags/"+this.mtSettings.hashtagName:"local"===this.mtSettings.timelineType&&(tp="public/local");let tg=''+this.mtSettings.btnSeeMore+"";tc.insertAdjacentHTML("beforeend",tg)}if(this.mtSettings.btnReload){let tu='";tc.insertAdjacentHTML("beforeend",tu);let tv=this.mtContainerNode.getElementsByClassName("btn-refresh")[0];tv.addEventListener("click",()=>{this.mtUpdate()})}}}#g(){this.mtBodyNode.addEventListener("click",t=>{"article"!=t.target.localName&&t.target.offsetParent?.localName!="article"&&("img"!=t.target.localName||t.target.parentNode.getAttribute("data-video-url"))||this.#u(t),"button"==t.target.localName&&t.target.classList.contains("mt-btn-spoiler")&&this.#s(t),("mt-post-media-play-icon"==t.target.className||"svg"==t.target.localName&&"mt-post-media-play-icon"==t.target.parentNode.className||"path"==t.target.localName&&"mt-post-media-play-icon"==t.target.parentNode.parentNode.className||"img"==t.target.localName&&t.target.parentNode.getAttribute("data-video-url"))&&this.#r(t)}),this.mtBodyNode.addEventListener("keydown",t=>{"Enter"===t.key&&"article"==t.target.localName&&this.#u(t)})}#u(tb){let tS=tb.target.closest(".mt-post").dataset.location;"a"!==tb.target.localName&&"span"!==tb.target.localName&&"button"!==tb.target.localName&&"time"!==tb.target.localName&&"mt-post-preview-noImage"!==tb.target.className&&"mt-post-avatar-image-big"!==tb.target.parentNode.className&&"mt-post-avatar-image-small"!==tb.target.parentNode.className&&"mt-post-preview-image"!==tb.target.parentNode.className&&"mt-post-preview"!==tb.target.parentNode.className&&tS&&window.open(tS,"_blank","noopener")}#f(){let t$=t=>{t.target.parentNode.classList.remove(this.mtSettings.spinnerClass),t.target.removeEventListener("load",t$),t.target.removeEventListener("error",t$)};this.mtBodyNode.querySelectorAll(`.${this.mtSettings.spinnerClass} > img`).forEach(t=>{t.addEventListener("load",t$),t.addEventListener("error",t$)})}#d(tf,t_){throw this.mtBodyNode.innerHTML='
    '+(t_||"❌")+'
    Oops, something\'s happened:
    '+tf+"
    ",this.mtBodyNode.setAttribute("role","none"),Error("Stopping the script due to an error building the timeline.")}} diff --git a/dist/mastodon-timeline.umd.js b/dist/mastodon-timeline.umd.js new file mode 100644 index 0000000..61ccc97 --- /dev/null +++ b/dist/mastodon-timeline.umd.js @@ -0,0 +1,8 @@ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).MastodonTimeline={})}(this,(function(t){"use strict"; +/** + * Mastodon embed timeline + * @author idotj + * @version 4.2.0 + * @url https://gitlab.com/idotj/mastodon-embed-timeline + * @license GNU AGPLv3 + */t.Init=class{constructor(t={}){this.defaultSettings={mtContainerId:"mt-container",instanceUrl:"https://mastodon.social",timelineType:"local",userId:"",profileName:"",hashtagName:"",spinnerClass:"mt-loading-spinner",defaultTheme:"auto",maxNbPostFetch:"20",maxNbPostShow:"20",hideUnlisted:!1,hideReblog:!1,hideReplies:!1,hideVideoPreview:!1,hidePreviewLink:!1,hideEmojos:!1,markdownBlockquote:!1,hideCounterBar:!1,txtMaxLines:"0",btnShowMore:"SHOW MORE",btnShowLess:"SHOW LESS",btnShowContent:"SHOW CONTENT",btnSeeMore:"See more posts at Mastodon",btnReload:"Refresh",insistSearchContainer:!1,insistSearchContainerTime:"3000"},this.mtSettings={...this.defaultSettings,...t},this.mtContainerNode="",this.mtBodyNode="",this.fetchedData={},this.#t((()=>{this.#e()}))}#t(t){"undefined"!=typeof document&&"complete"===document.readyState?t():"undefined"!=typeof document&&"complete"!==document.readyState&&document.addEventListener("DOMContentLoaded",t())}#e(){const t=()=>{this.mtContainerNode=document.getElementById(this.mtSettings.mtContainerId),this.mtBodyNode=this.mtContainerNode.getElementsByClassName("mt-body")[0],this.#s(),this.#i("newTimeline")};if(this.mtSettings.insistSearchContainer){const e=performance.now(),s=()=>{if(document.getElementById(this.mtSettings.mtContainerId))t();else{performance.now()-e container with id: "${this.mtSettings.mtContainerId}" after several attempts for ${this.mtSettings.insistSearchContainerTime/1e3} seconds`)}};s()}else document.getElementById(this.mtSettings.mtContainerId)?t():console.error(`Impossible to find the
    container with id: "${this.mtSettings.mtContainerId}". Please try to add the option 'insistSearchContainer: true' when initializing the script`)}mtUpdate(){this.#t((()=>{this.mtBodyNode.replaceChildren(),this.mtBodyNode.insertAdjacentHTML("afterbegin",'
    '),this.#i("updateTimeline")}))}mtColorTheme(t){this.#t((()=>{this.mtContainerNode.setAttribute("data-theme",t)}))}#s(){if("auto"===this.mtSettings.defaultTheme){let t=window.matchMedia("(prefers-color-scheme: dark)");t.matches?this.mtColorTheme("dark"):this.mtColorTheme("light"),t.addEventListener("change",(t=>{t.matches?this.mtColorTheme("dark"):this.mtColorTheme("light")}))}else this.mtColorTheme(this.mtSettings.defaultTheme)}#a(){return new Promise(((t,e)=>{let s={};this.mtSettings.instanceUrl?"profile"===this.mtSettings.timelineType?this.mtSettings.userId?s.timeline=`${this.mtSettings.instanceUrl}/api/v1/accounts/${this.mtSettings.userId}/statuses?limit=${this.mtSettings.maxNbPostFetch}`:this.#o("Please check your userId value","⚠ī¸"):"hashtag"===this.mtSettings.timelineType?this.mtSettings.hashtagName?s.timeline=`${this.mtSettings.instanceUrl}/api/v1/timelines/tag/${this.mtSettings.hashtagName}?limit=${this.mtSettings.maxNbPostFetch}`:this.#o("Please check your hashtagName value","⚠ī¸"):"local"===this.mtSettings.timelineType?s.timeline=`${this.mtSettings.instanceUrl}/api/v1/timelines/public?local=true&limit=${this.mtSettings.maxNbPostFetch}`:this.#o("Please check your timelineType value","⚠ī¸"):this.#o("Please check your instanceUrl value","⚠ī¸"),this.mtSettings.hideEmojos||(s.emojos=this.mtSettings.instanceUrl+"/api/v1/custom_emojis");const i=Object.entries(s).map((([t,s])=>async function(t){const e=await fetch(t);if(!e.ok)throw new Error("Failed to fetch the following Url:
    "+t+"
    Error status: "+e.status+"
    Error message: "+e.statusText);return await e.json()}(s).then((e=>({[t]:e}))).catch((i=>(e(new Error("Something went wrong fetching data from: "+s)),this.#o(i.message),{[t]:[]})))));Promise.all(i).then((e=>{this.mtSettings.fetchedData=e.reduce(((t,e)=>({...t,...e})),{}),t()}))}))}async#i(t){await this.#a(),this.mtBodyNode.replaceChildren();let e=0;for(let t in this.mtSettings.fetchedData.timeline)("public"==this.mtSettings.fetchedData.timeline[t].visibility||!this.mtSettings.hideUnlisted&&"unlisted"==this.mtSettings.fetchedData.timeline[t].visibility)&&(this.mtSettings.hideReblog&&this.mtSettings.fetchedData.timeline[t].reblog||this.mtSettings.hideReplies&&this.mtSettings.fetchedData.timeline[t].in_reply_to_id||eThis may be due to an incorrect configuration in the parameters or to filters applied (to hide certains type of posts)";this.#o(t,"📭")}else"newTimeline"===t?(this.#r(),this.#l(),this.#m()):"updateTimeline"===t?this.#r():this.#o("The function buildTimeline() was expecting a param")}#n(t,e){this.mtBodyNode.insertAdjacentHTML("beforeend",this.#d(t,e))}#d(t,e){let s,i,a,o,n,r,l,m,d;t.reblog?(o=t.reblog.url,s='
    '+this.#c(t.reblog.account.username)+' avatar
    '+this.#c(t.account.username)+' avatar
    ',a=t.reblog.account.display_name?t.reblog.account.display_name:t.reblog.account.username,this.mtSettings.hideEmojos||(a=this.#h(a,this.mtSettings.fetchedData.emojos)),i='
    '+a+' account
    ',n=t.reblog.created_at,d=t.reblog.replies_count,m=t.reblog.reblogs_count,l=t.reblog.favourites_count):(o=t.url,s='
    '+this.#c(t.account.username)+' avatar
    ',a=t.account.display_name?t.account.display_name:t.account.username,this.mtSettings.hideEmojos||(a=this.#h(a,this.mtSettings.fetchedData.emojos)),i='
    '+a+' account
    ',n=t.created_at,d=t.replies_count,m=t.reblogs_count,l=t.favourites_count),r=this.#p(n);const c='
    ";let h="";"0"!==this.mtSettings.txtMaxLines&&(h=" truncate",this.mtBodyNode.parentNode.style.setProperty("--mt-txt-max-lines",this.mtSettings.txtMaxLines));let p="";p=""!==t.spoiler_text?'
    '+t.spoiler_text+'
    '+this.#g(t.content)+"
    ":t.reblog&&""!==t.reblog.content&&""!==t.reblog.spoiler_text?'
    '+t.reblog.spoiler_text+'
    '+this.#g(t.reblog.content)+"
    ":t.reblog&&""!==t.reblog.content&&""===t.reblog.spoiler_text?'
    '+this.#g(t.reblog.content)+"
    ":'
    '+this.#g(t.content)+"
    ";let g=[];if(t.media_attachments.length>0)for(let e in t.media_attachments)g.push(this.#u(t.media_attachments[e],t.sensitive));if(t.reblog&&t.reblog.media_attachments.length>0)for(let e in t.reblog.media_attachments)g.push(this.#u(t.reblog.media_attachments[e],t.reblog.sensitive));let u="";!this.mtSettings.hidePreviewLink&&t.card&&(u=this.#v(t.card));let v="";if(t.poll){let e="";for(let s in t.poll.options)e+="
  • "+t.poll.options[s].title+"
  • ";v='
      '+e+"
    "}let b="";if(!this.mtSettings.hideCounterBar){b='
    '+('
    '+d+"
    ")+('
    '+m+"
    ")+('
    '+l+"
    ")+"
    "}return'
    '+s+i+c+"
    "+p+g.join("")+u+v+b+"
    "}#g(t){let e=t;return e=this.#b(e),this.mtSettings.hideEmojos||(e=this.#h(e,this.mtSettings.fetchedData.emojos)),this.mtSettings.markdownBlockquote&&(e=this.#f(e,"

    >","

    ","

    ","

    ")),e}#b(t){let e=t.replaceAll('rel="tag"','rel="tag" target="_blank"');return e=e.replaceAll('class="u-url mention"','class="u-url mention" target="_blank"'),e}#f(t,e,s,i,a){if(t.includes(e)){const o=new RegExp(e+"(.*?)"+s,"gi");return t.replace(o,i+"$1"+a)}return t}#c(t){return(t??"").replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'")}#h(t,e){if(t.includes(":")){for(const s of e){const e=new RegExp(`\\:${s.shortcode}\\:`,"g");t=t.replace(e,`Emoji ${s.shortcode}`)}return t}return t}#p(t){const e=new Date(t);return["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"][e.getMonth()]+" "+e.getDate()+", "+e.getFullYear()}#u(t,e){const s=e||!1,i=t.type;let a="";return"image"===i&&(a='
    '+(s?'":"")+''+(t.description?this.#c(t.description):
    '),"audio"===i&&(a=t.preview_url?'
    '+(s?'":"")+''+(t.description?this.#c(t.description):
    ':'
    '+(s?'":"")+'
    '),"video"!==i&&"gifv"!==i||(a=this.mtSettings.hideVideoPreview?'
    '+(s?'":"")+'
    ':'
    '+(s?'":"")+''+(t.description?this.#c(t.description):
    '),a}#S(t){const e=t.target.closest("[data-video-url]"),s=e.dataset.videoUrl;e.replaceChildren(),e.innerHTML=''}#w(t){const e=t.target.nextSibling;"img"===e.localName||"audio"===e.localName||"video"===e.localName?(t.target.parentNode.classList.remove("mt-post-media-spoiler"),t.target.style.display="none"):(e.classList.contains("spoiler-txt-hidden")||e.classList.contains("spoiler-txt-visible"))&&(t.target.textContent==this.mtSettings.btnShowMore?(e.classList.remove("spoiler-txt-hidden"),e.classList.add("spoiler-txt-visible"),t.target.setAttribute("aria-expanded","true"),t.target.textContent=this.mtSettings.btnShowLess):(e.classList.remove("spoiler-txt-visible"),e.classList.add("spoiler-txt-hidden"),t.target.setAttribute("aria-expanded","false"),t.target.textContent=this.mtSettings.btnShowMore))}#v(t){return''+(t.image?'
    '+this.#c(t.image_description)+'
    ':'
    📄
    ')+'
    '+(t.provider_name?''+this.#y(t.provider_name)+"":"")+''+t.title+""+(t.author_name?''+this.#y(t.author_name)+"":"")+"
    "}#y(t){return(new DOMParser).parseFromString(t,"text/html").body.textContent}#m(){if(this.mtSettings.btnSeeMore||this.mtSettings.btnReload){this.mtBodyNode.parentNode.insertAdjacentHTML("beforeend",'');const t=this.mtContainerNode.getElementsByClassName("mt-footer")[0];if(this.mtSettings.btnSeeMore){let e="";"profile"===this.mtSettings.timelineType?this.mtSettings.profileName?e=this.mtSettings.profileName:this.#o("Please check your profileName value","⚠ī¸"):"hashtag"===this.mtSettings.timelineType?e="tags/"+this.mtSettings.hashtagName:"local"===this.mtSettings.timelineType&&(e="public/local");const s=''+this.mtSettings.btnSeeMore+"";t.insertAdjacentHTML("beforeend",s)}if(this.mtSettings.btnReload){const e='";t.insertAdjacentHTML("beforeend",e);this.mtContainerNode.getElementsByClassName("btn-refresh")[0].addEventListener("click",(()=>{this.mtUpdate()}))}}}#l(){this.mtBodyNode.addEventListener("click",(t=>{("article"==t.target.localName||"article"==t.target.offsetParent?.localName||"img"==t.target.localName&&!t.target.parentNode.getAttribute("data-video-url"))&&this.#N(t),"button"==t.target.localName&&t.target.classList.contains("mt-btn-spoiler")&&this.#w(t),("mt-post-media-play-icon"==t.target.className||"svg"==t.target.localName&&"mt-post-media-play-icon"==t.target.parentNode.className||"path"==t.target.localName&&"mt-post-media-play-icon"==t.target.parentNode.parentNode.className||"img"==t.target.localName&&t.target.parentNode.getAttribute("data-video-url"))&&this.#S(t)})),this.mtBodyNode.addEventListener("keydown",(t=>{"Enter"===t.key&&"article"==t.target.localName&&this.#N(t)}))}#N(t){const e=t.target.closest(".mt-post").dataset.location;"a"!==t.target.localName&&"span"!==t.target.localName&&"button"!==t.target.localName&&"time"!==t.target.localName&&"mt-post-preview-noImage"!==t.target.className&&"mt-post-avatar-image-big"!==t.target.parentNode.className&&"mt-post-avatar-image-small"!==t.target.parentNode.className&&"mt-post-preview-image"!==t.target.parentNode.className&&"mt-post-preview"!==t.target.parentNode.className&&e&&window.open(e,"_blank","noopener")}#r(){const t=e=>{e.target.parentNode.classList.remove(this.mtSettings.spinnerClass),e.target.removeEventListener("load",t),e.target.removeEventListener("error",t)};this.mtBodyNode.querySelectorAll(`.${this.mtSettings.spinnerClass} > img`).forEach((e=>{e.addEventListener("load",t),e.addEventListener("error",t)}))}#o(t,e){const s=e||"❌";throw this.mtBodyNode.innerHTML='
    '+s+'
    Oops, something\'s happened:
    '+t+"
    ",this.mtBodyNode.setAttribute("role","none"),new Error("Stopping the script due to an error building the timeline.")}}})); diff --git a/docker-compose.yml b/docker-compose.yml index 5b7092b..c4c66e3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,28 +1,23 @@ -# This is a docker compose file to demonstrate and test the mastodon-timeline widget -# Run this docker CLI from the root repo directory: -# -# $ docker compose up - -version: "3.3" - -services: - lighttpd: - image: jitesoft/lighttpd - ports: - - "8080:80" - - "8443:443" - volumes: - - ./src/mastodon-timeline.css:/var/www/src/mastodon-timeline.css - - ./src/mastodon-timeline.js:/var/www/src/mastodon-timeline.js - - ./examples/local-timeline.html:/var/www/index.html - - ./examples/profile-timeline.html:/var/www/profile-timeline.html - - ./examples/hashtag-timeline.html:/var/www/hashtag-timeline.html - - ./examples/theme-timeline.html:/var/www/theme-timeline.html - - ./examples/multiple-timelines.html:/var/www/multiple-timelines.html - environment: - - PORT=80 - - SERVER_NAME=mastodon-timeline - - SERVER_ROOT=/var/www - - CONFIG_FILE=/etc/lighttpd/lighttpd.conf - - SKIP_HEALTHCHECK=false - - MAX_FDS=1024 +# This is a docker compose file to demonstrate and test the Mastodon embed timeline widget +# Run this docker CLI from the root repository directory: +# +# $ docker compose up + +version: "3.3" + +services: + lighttpd: + image: jitesoft/lighttpd + ports: + - "8080:80" + - "8443:443" + volumes: + - ./dist:/var/www/dist + - ./examples:/var/www/examples + environment: + - PORT=80 + - SERVER_NAME=mastodon-timeline + - SERVER_ROOT=/var/www + - CONFIG_FILE=/etc/lighttpd/lighttpd.conf + - SKIP_HEALTHCHECK=false + - MAX_FDS=1024 diff --git a/examples/hashtag-timeline.html b/examples/hashtag-timeline.html index 92acf08..7c5aa03 100644 --- a/examples/hashtag-timeline.html +++ b/examples/hashtag-timeline.html @@ -8,7 +8,7 @@ - + + + + +
    + +
    +

    🐘 Mastodon embed timeline

    +

    Local timeline (Javascript module)

    +

    + This example shows 20 posts from the following instance: +
    + mastodon.online +

    +

    + It loads the Javascript file as a module and has been initialized with + the following script: +

    +
    +        
    +  <script type="module">
    +    import * as MastodonTimeline from '../dist/mastodon-timeline.esm.js'
    +    const myTimeline = new MastodonTimeline.Init({
    +      instanceUrl: "https://mastodon.online"
    +    });
    +  </script>
    +          
    +        
    + +
    + +

    + If you get an error in your browser console related with CORS: + "Cross-Origin Request Blocked: The Same Origin Policy disallows + reading the remote resource..." + I recommend to check the + CONTRIBUTING.md + document and setup a local environment to test. +

    +
    + +
    + +
    +
    +
    +
    +
    +
    +
    + + + + + diff --git a/examples/local-timeline.html b/examples/local-timeline.html index 8cef5d2..00d7c85 100644 --- a/examples/local-timeline.html +++ b/examples/local-timeline.html @@ -8,7 +8,7 @@ - +