Merge branch 'pinned-posts' into 'master'

Show pinned posts

See merge request idotj/mastodon-embed-timeline!30
This commit is contained in:
i.j 2024-02-29 10:00:33 +00:00
commit 02cc00089d
12 changed files with 1487 additions and 1214 deletions

View File

@ -9,4 +9,6 @@ deploy:
- echo "@scope:registry=https://${CI_SERVER_HOST}/api/v4/projects/${CI_PROJECT_ID}/packages/npm/" > .npmrc - echo "@scope:registry=https://${CI_SERVER_HOST}/api/v4/projects/${CI_PROJECT_ID}/packages/npm/" > .npmrc
- echo "//${CI_SERVER_HOST}/api/v4/projects/${CI_PROJECT_ID}/packages/npm/:_authToken=${CI_JOB_TOKEN}" >> .npmrc - echo "//${CI_SERVER_HOST}/api/v4/projects/${CI_PROJECT_ID}/packages/npm/:_authToken=${CI_JOB_TOKEN}" >> .npmrc
- npm publish - npm publish
only:
- master
environment: production environment: production

View File

@ -1,3 +1,12 @@
v4.3.1 - 01/03/2024
- Show pinned posts
- Add icon to pinned posts
- Add '*' character to edited posts
- Use Intl.DateTimeFormat for date formatting
- Allow to customize date by locale/options parameters
- Show user account under user name
- Added a new customized HTML example
v4.2.1 - 26/02/2024 v4.2.1 - 26/02/2024
- Changed project name: mastodon-embed-feed-timeline -> mastodon-embed-timeline - Changed project name: mastodon-embed-feed-timeline -> mastodon-embed-timeline
- Improved DOM load for module purposes - Improved DOM load for module purposes

140
README.md
View File

@ -1,4 +1,4 @@
# 🐘 Mastodon embed timeline (new v4.2) # 🐘 Mastodon embed timeline
![Mastodon timeline widget screenshot](screenshot-light-dark.jpg "Mastodon timeline widget screenshot") ![Mastodon timeline widget screenshot](screenshot-light-dark.jpg "Mastodon timeline widget screenshot")
@ -24,27 +24,29 @@ Demo running:
## Installation ## Installation
You have three different ways to install it in your project, choose the one that best suits your needs:
### Download ### Download
Ready-to-use compiled and minified files to easily start. Download into your project the following compiled and minified files:
- Download into your project the following files: - `dist/mastodon-timeline.min.css`
- `dist/mastodon-timeline.min.css` - `dist/mastodon-timeline.umd.js`
- `dist/mastodon-timeline.umd.js`
Now call the CSS and JS files in your HTML page using the `<link>` and `<script>` tags as follows in this example: Now call the CSS and JS files in your HTML page using the `<link>` and `<script>` tags as follows in this example:
```html ```html
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Your site title</title> <title>Your site title</title>
<!-- CSS --> <!-- CSS -->
<link href="path/to/mastodon-timeline.min.css" rel="stylesheet" /> <link href="path/to/mastodon-timeline.min.css" rel="stylesheet" />
</head> </head>
<body> <body>
<!-- Your HTML content --> <!-- Your HTML content -->
@ -59,20 +61,20 @@ Now call the CSS and JS files in your HTML page using the `<link>` and `<script>
### CDN ### CDN
This option allows you to get started quickly without the need to upload any files into your server. This option allows you to start without the need to upload any files on your server.
Copy the following CSS and JS to include 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.2.1/dist/mastodon-timeline.min.css" integrity="sha256-8K/cHh6W5bGXJHrkHD8gXe+Z3pe1zt5Zsq2eC1MQbx4=" crossorigin="anonymous"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@idotj/mastodon-embed-timeline@4.3.1/dist/mastodon-timeline.min.css" integrity="sha256-8K/cHh6W5bGXJHrkHD8gXe+Z3pe1zt5Zsq2eC1MQbx4=" crossorigin="anonymous" />
``` ```
```html ```html
<script src="https://cdn.jsdelivr.net/npm/@idotj/mastodon-embed-timeline@4.2.1/dist/mastodon-timeline.umd.js" integrity="sha256-G5WmanubPwBlbI/BnihBUXGfZqjppxj/1jB9kR2SEYA=" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/@idotj/mastodon-embed-timeline@4.3.1/dist/mastodon-timeline.umd.js" integrity="sha256-G5WmanubPwBlbI/BnihBUXGfZqjppxj/1jB9kR2SEYA=" crossorigin="anonymous"></script>
``` ```
### Package manager ### Package manager
Install your Mastodon timeline using npm or yarn: A quick way to get it installed using **npm** or **yarn**:
```terminal ```terminal
npm install @idotj/mastodon-embed-timeline npm install @idotj/mastodon-embed-timeline
@ -84,19 +86,19 @@ or
yarn add @idotj/mastodon-embed-timeline yarn add @idotj/mastodon-embed-timeline
``` ```
After installation, you can import the widget as follows: After installation, you can import the Javascript as follows:
```js ```js
import * as MastodonTimeline from "@idotj/mastodon-embed-timeline"; 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. Make sure to import also the file `mastodon-timeline.min.css` into your project.
## Setup ## Setup
### Initialize ### Initialize
The first step to get your timeline up is to add the following HTML structure in your page: To get your timeline up add the following HTML structure in your page:
```html ```html
<div id="mt-container" class="mt-container"> <div id="mt-container" class="mt-container">
@ -124,11 +126,11 @@ window.addEventListener("load", () => {
}); });
``` ```
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**: The next step is to configure the options/values of your timeline according to the type you prefer. There are three types, **Local**, **Profile** and **Hashtag**. Here you have an example of each one to see how it works:
#### Local timeline #### Local timeline
Add the following option/value when initializing the timeline: To show a timeline with posts from the instance [mastodon.online](https://mastodon.online/public/local) add the following option/value when initializing the timeline:
```js ```js
const myTimeline = new MastodonTimeline.Init({ const myTimeline = new MastodonTimeline.Init({
@ -136,11 +138,9 @@ const myTimeline = new MastodonTimeline.Init({
}); });
``` ```
It will show a timeline with posts from the instance [mastodon.online](https://mastodon.online/public/local)
#### Profile timeline #### Profile timeline
Add the following options/values when initializing the timeline: To show a timeline with posts from my Mastodon profile [@idotj](https://mastodon.online/@idotj) add the following options/values when initializing the timeline:
```js ```js
const myTimeline = new MastodonTimeline.Init({ const myTimeline = new MastodonTimeline.Init({
@ -151,8 +151,6 @@ const myTimeline = new MastodonTimeline.Init({
}); });
``` ```
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: 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: - 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:
@ -164,7 +162,7 @@ It will show a timeline with posts from my Mastodon profile [@idotj](https://mas
#### Hashtag timeline #### Hashtag timeline
Add the following options/values when initializing the timeline: To show a timeline with posts containing the hashtag [#fediverse](https://mastodon.online/tags/fediverse) add the following options/values when initializing the timeline:
```js ```js
const myTimeline = new MastodonTimeline.Init({ const myTimeline = new MastodonTimeline.Init({
@ -174,68 +172,106 @@ const myTimeline = new MastodonTimeline.Init({
}); });
``` ```
It will show a timeline with posts containing the hashtag [#fediverse](https://mastodon.online/tags/fediverse)
### Customize ### Customize
You can pass more options/values to personalize your timeline. Here you have all the available options: In the `examples/` folder there is an HTML file `local-timeline-customized.html` where you can see how to customize your timeline by overwriting the CSS styles and using various JS options when initializing the timeline.
If you need to change something in the core files (`src/` folder), I recommend you to read the document [CONTRIBUTING.md](https://gitlab.com/idotj/mastodon-embed-timeline/-/blob/master/CONTRIBUTING.md#testing) to see how to compile and test your changes.
Here you have all the options available to quickly setup and customize your timeline:
```js ```js
// Id of the <div> containing the timeline // Id of the <div> containing the timeline
mtContainerId: "mt-container", mtContainerId: "mt-container",
// Mastodon instance Url (including https://) // Mastodon instance Url including https://
instanceUrl: "https://mastodon.social", instanceUrl: "https://mastodon.social",
// Choose type of posts to show in the timeline: 'local', 'profile', 'hashtag'. Default: local // Choose type of posts to show in the timeline: 'local', 'profile', 'hashtag'
// Default: local
timelineType: "local", timelineType: "local",
// Your user ID number on Mastodon instance. Leave it empty if you didn't choose 'profile' as type of timeline // Your user ID number on Mastodon instance
// Leave it empty if you didn't choose 'profile' as type of timeline
userId: "", 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 // 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: "", profileName: "",
// The name of the hashtag (not including the # symbol). Leave it empty if you didn't choose 'hashtag' as type of timeline // The name of the hashtag (not including the # symbol)
// Leave it empty if you didn't choose 'hashtag' as type of timeline
hashtagName: "", hashtagName: "",
// Class name for the loading spinner (also used in CSS file) // Class name for the loading spinner (also used in CSS file)
spinnerClass: "mt-loading-spinner", spinnerClass: "mt-loading-spinner",
// Preferred color theme: 'light', 'dark' or 'auto'. Default: auto // Preferred color theme: 'light', 'dark' or 'auto'
// Default: auto
defaultTheme: "auto", defaultTheme: "auto",
// Maximum number of posts to request to the server. Default: 20 // Maximum number of posts to request to the server
// Default: 20
maxNbPostFetch: "20", maxNbPostFetch: "20",
// Maximum number of posts to show in the timeline. Default: 20 // Maximum number of posts to show in the timeline
// Default: 20
maxNbPostShow: "20", maxNbPostShow: "20",
// Hide unlisted posts. Default: don't hide // Specifies the format of the date according to the chosen language/country
// Default: British English (day-month-year order)
dateLocale: "en-GB",
// Customize the date format using the options
// Default: DD MMM YYYY
dateOptions: {
day: "2-digit",
month: "short",
year: "numeric",
},
// Hide unlisted posts
// Default: don't hide
hideUnlisted: false, hideUnlisted: false,
// Hide boosted posts. Default: don't hide // Hide boosted posts
// Default: don't hide
hideReblog: false, hideReblog: false,
// Hide replies posts. Default: don't hide // Hide replies posts
// Default: don't hide
hideReplies: false, hideReplies: false,
// Hide video image preview and load video player instead. Default: don't hide // Hide pinned posts from the profile timeline
hideVideoPreview: false, // Default: don't hide
hidePinnedPosts: false,
// Hide preview card if post contains a link, photo or video from a Url. Default: don't hide // Hide user account under the user name
hidePreviewLink: false, // Default: don't hide
hideUserAccount: false,
// Hide custom emojis available on the server. Default: don't hide // Hide custom emojis available on the server
// Default: don't hide
hideEmojos: false, hideEmojos: false,
// Converts Markdown symbol ">" at the beginning of a paragraph into a blockquote HTML tag. Default: don't apply // Hide video image preview and load video player instead
markdownBlockquote: false, // Default: don't hide
hideVideoPreview: false,
// Hide replies, boosts and favourites posts counter. Default: don't hide // Hide preview card if post contains a link, photo or video from a Url
// Default: don't hide
hidePreviewLink: false,
// Hide replies, boosts and favourites posts counter
// Default: don't hide
hideCounterBar: false, hideCounterBar: false,
// Limit the text content to a maximum number of lines. Default: 0 (unlimited) // Converts Markdown symbol ">" at the beginning of a paragraph into a blockquote HTML tag
// Default: don't apply
markdownBlockquote: false,
// Limit the text content to a maximum number of lines
// Default: 0 (unlimited)
txtMaxLines: "0", txtMaxLines: "0",
// Customize the text of the button used for showing/hiding sensitive/spolier text // Customize the text of the button used for showing/hiding sensitive/spolier text
@ -245,16 +281,20 @@ You can pass more options/values to personalize your timeline. Here you have all
// Customize the text of the button used for showing sensitive/spolier media content // Customize the text of the button used for showing sensitive/spolier media content
btnShowContent: "SHOW 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 // 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", 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 // 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", btnReload: "Refresh",
// Keep searching for the main <div> container before building the timeline. Useful in some cases where extra time is needed to render the page. Default: false // Keep searching for the main <div> container before building the timeline. Useful in some cases where extra time is needed to render the page
// Default: don't apply
insistSearchContainer: false, insistSearchContainer: false,
// Defines the maximum time to continue searching for the main <div> container. Default: 3 seconds // Defines the maximum time to continue searching for the main <div> container
// Default: 3 seconds
insistSearchContainerTime: "3000", insistSearchContainerTime: "3000",
``` ```
@ -270,7 +310,7 @@ You can pass more options/values to personalize your timeline. Here you have all
The folder `examples/` contains several demos in HTML to play with. Download the full project and open each HTML file in your favorite browser. 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. 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 options such as Docker or Http-server.
## 🌐 Browser support ## 🌐 Browser support

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -67,7 +67,7 @@
<!-- Title + Explained details of the example --> <!-- Title + Explained details of the example -->
<div class="dummy-wrapper-text"> <div class="dummy-wrapper-text">
<h1>🐘 Mastodon embed timeline</h1> <h1>🐘 Mastodon embed timeline</h1>
<h2>Profile timeline</h2> <h2>Hashtag timeline</h2>
<p> <p>
This example shows posts containing the hashtag This example shows posts containing the hashtag
<br /> <br />

View File

@ -0,0 +1,169 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Mastodon embed timeline</title>
<meta name="author" content="i.j" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="keywords" content="mastodon, embed timeline" />
<meta name="description" content="Mastodon embed timeline" />
<link rel="shortcut icon" href="#" />
<link rel="stylesheet" href="../dist/mastodon-timeline.min.css" />
<style>
* {
box-sizing: border-box;
}
html {
height: 100%;
}
body {
height: 100%;
background: lightgrey;
font-family: Arial, Helvetica, sans-serif;
font-size: 16px;
margin: 0;
}
.dummy-main-container {
display: flex;
flex-direction: row;
gap: 2rem;
height: 100%;
justify-content: center;
align-items: center;
padding: 1rem;
}
.dummy-wrapper-text,
.dummy-wrapper-timeline {
width: 50%;
max-width: 30rem;
height: calc(100% - 4rem);
padding: 0 1rem;
}
.dummy-wrapper-text h1,
.dummy-wrapper-text h2,
.dummy-wrapper-text p {
margin: 0 0 1rem 0;
}
.dummy-wrapper-text pre {
display: flex;
background: lightsteelblue;
border-left: 3px solid #563acc;
color: midnightblue;
page-break-inside: avoid;
font-family: monospace;
line-height: 1.5;
max-width: 100%;
overflow: auto;
word-wrap: break-word;
}
.dummy-wrapper-text hr {
margin: 2rem 0;
}
/* Customized CSS styles */
.mt-container {
background-color: transparent;
border: 1px solid white;
}
.mt-post {
border-bottom: none;
box-shadow: 2px 2px 6px 2px rgba(0, 0, 0, 0.25);
margin-bottom: 1rem;
}
.mt-post-avatar-image-big img {
border-radius: 0;
}
.mt-container a,
.mt-container a:active,
.mt-container a:link {
color: darkgreen;
}
</style>
</head>
<body>
<div class="dummy-main-container">
<!-- Title + Explained details of the example -->
<div class="dummy-wrapper-text">
<h1>🐘 Mastodon embed timeline</h1>
<h2>Local timeline (customized)</h2>
<p>
This example shows 10 posts from the following instance:
<br />
<a
href="https://mastodon.social/public/local"
target="_blank"
rel="nofollow noopener noreferrer"
>mastodon.social</a
>
</p>
<p>
Contains several CSS styles that change its appearance without the
need to modify the original CSS file (inspect the code at the
beginning of the HTML file to see the changes).
</p>
<p>
At JS level, it defaults to the light theme and the date is displayed
in US format using digits only. In order to achieve a minimalist
style, the following options have been changed at its
initialization:
</p>
<pre>
<code>
&lt;script&gt;
const myTimeline = new MastodonTimeline.Init({
instanceUrl: "https://mastodon.online",
defaultTheme: "light",
dateLocale: "en-CA",
dateOptions: {
day: "2-digit",
month: "2-digit",
year: "numeric",
},
hideReplies: true,
hideUserAccount: true,
hidePreviewLink: true,
hideCounterBar: true,
txtMaxLines: "3",
btnSeeMore: "",
btnReload: ""
});
&lt;/script&gt;
</code>
</pre>
</div>
<div class="dummy-wrapper-timeline">
<!-- Mastodon Timeline -->
<div id="mt-container" class="mt-container">
<div class="mt-body" role="feed">
<div class="mt-loading-spinner"></div>
</div>
</div>
</div>
</div>
<!-- JavaScript -->
<script src="../dist/mastodon-timeline.umd.js"></script>
<script>
const myTimeline = new MastodonTimeline.Init({
instanceUrl: "https://mastodon.online",
defaultTheme: "light",
maxNbPostShow: "10",
dateLocale: "en-CA",
dateOptions: {
day: "2-digit",
month: "2-digit",
year: "numeric",
},
hideReplies: true,
hideUserAccount: true,
hidePreviewLink: true,
hideCounterBar: true,
txtMaxLines: "3",
btnSeeMore: "",
btnReload: "",
});
</script>
</body>
</html>

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "@idotj/mastodon-embed-timeline", "name": "@idotj/mastodon-embed-timeline",
"version": "4.2.1", "version": "4.3.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@idotj/mastodon-embed-timeline", "name": "@idotj/mastodon-embed-timeline",
"version": "4.2.1", "version": "4.3.1",
"license": "GNU", "license": "GNU",
"devDependencies": { "devDependencies": {
"@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-terser": "^0.4.4",

View File

@ -1,6 +1,6 @@
{ {
"name": "@idotj/mastodon-embed-timeline", "name": "@idotj/mastodon-embed-timeline",
"version": "4.2.1", "version": "4.3.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": {

View File

@ -1,4 +1,4 @@
/* Mastodon embed timeline v4.2.1 */ /* Mastodon embed timeline v4.3.1 */
/* More info at: */ /* More info at: */
/* https://gitlab.com/idotj/mastodon-embed-timeline */ /* https://gitlab.com/idotj/mastodon-embed-timeline */
@ -151,22 +151,42 @@
margin-bottom: 1rem; margin-bottom: 1rem;
} }
.mt-post-header-user { .mt-post-header-user {
font-weight: 600; overflow: hidden;
margin-top: 0.5rem; padding-right: 0.75rem;
padding-right: 1rem;
} }
.mt-post-header-user > a { .mt-post-header-user > a {
display: flex;
align-items: flex-start;
color: var(--mt-color-content-txt) !important; color: var(--mt-color-content-txt) !important;
overflow-wrap: anywhere; overflow-wrap: anywhere;
} }
.mt-container .mt-post-header-user > a:hover {
text-decoration: none;
}
.mt-post-header-user-name {
font-weight: 600;
}
.mt-container .mt-post-header-user:hover .mt-post-header-user-name {
text-decoration: underline;
}
.mt-post-header-user-account {
display: block;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
color: var(--mt-color-contrast-gray);
}
.mt-post-header-date { .mt-post-header-date {
display: flex;
font-size: 0.75rem; font-size: 0.75rem;
text-align: right; text-align: right;
margin: 0.5rem 0 0 auto; margin-left: auto;
} }
.mt-post-header-date > a { .mt-post-header-date .mt-post-pinned {
width: 1.25rem;
margin-top: -0.25rem;
fill: var(--mt-color-contrast-gray);
}
.mt-container .mt-post-header-date > a {
white-space: nowrap;
color: var(--mt-color-contrast-gray) !important; color: var(--mt-color-contrast-gray) !important;
} }

View File

@ -1,7 +1,7 @@
/** /**
* Mastodon embed timeline * Mastodon embed timeline
* @author idotj * @author idotj
* @version 4.2.1 * @version 4.3.1
* @url https://gitlab.com/idotj/mastodon-embed-timeline * @url https://gitlab.com/idotj/mastodon-embed-timeline
* @license GNU AGPLv3 * @license GNU AGPLv3
*/ */
@ -20,14 +20,22 @@ export class Init {
defaultTheme: "auto", defaultTheme: "auto",
maxNbPostFetch: "20", maxNbPostFetch: "20",
maxNbPostShow: "20", maxNbPostShow: "20",
dateLocale: "en-GB",
dateOptions: {
day: "2-digit",
month: "short",
year: "numeric",
},
hideUnlisted: false, hideUnlisted: false,
hideReblog: false, hideReblog: false,
hideReplies: false, hideReplies: false,
hidePinnedPosts: false,
hideUserAccount: false,
hideEmojos: false,
hideVideoPreview: false, hideVideoPreview: false,
hidePreviewLink: false, hidePreviewLink: false,
hideEmojos: false,
markdownBlockquote: false,
hideCounterBar: false, hideCounterBar: false,
markdownBlockquote: false,
txtMaxLines: "0", txtMaxLines: "0",
btnShowMore: "SHOW MORE", btnShowMore: "SHOW MORE",
btnShowLess: "SHOW LESS", btnShowLess: "SHOW LESS",
@ -198,6 +206,9 @@ export class Init {
if (this.mtSettings.timelineType === "profile") { if (this.mtSettings.timelineType === "profile") {
if (this.mtSettings.userId) { if (this.mtSettings.userId) {
urls.timeline = `${this.mtSettings.instanceUrl}/api/v1/accounts/${this.mtSettings.userId}/statuses?limit=${this.mtSettings.maxNbPostFetch}`; urls.timeline = `${this.mtSettings.instanceUrl}/api/v1/accounts/${this.mtSettings.userId}/statuses?limit=${this.mtSettings.maxNbPostFetch}`;
if (!this.mtSettings.hidePinnedPosts) {
urls.pinned = `${this.mtSettings.instanceUrl}/api/v1/accounts/${this.mtSettings.userId}/statuses?pinned=true`;
}
} else { } else {
this.#showError( this.#showError(
"Please check your <strong>userId</strong> value", "Please check your <strong>userId</strong> value",
@ -245,11 +256,11 @@ export class Init {
// Fetch all urls simultaneously // Fetch all urls simultaneously
Promise.all(urlsPromises).then((dataObjects) => { Promise.all(urlsPromises).then((dataObjects) => {
this.mtSettings.fetchedData = dataObjects.reduce((result, dataItem) => { this.fetchedData = dataObjects.reduce((result, dataItem) => {
return { ...result, ...dataItem }; return { ...result, ...dataItem };
}, {}); }, {});
// console.log("Mastodon timeline data fetched: ", this.mtSettings.fetchedData); // console.log("Mastodon timeline data fetched: ", this.fetchedData);
resolve(); resolve();
}); });
}); });
@ -262,33 +273,43 @@ export class Init {
async #buildTimeline(t) { async #buildTimeline(t) {
await this.#fetchTimelineData(); await this.#fetchTimelineData();
// Merge pinned posts with timeline posts
let posts;
if (
!this.mtSettings.hidePinnedPosts &&
this.fetchedData.pinned?.length !== undefined &&
this.fetchedData.pinned.length !== 0
) {
const pinnedPosts = this.fetchedData.pinned.map((obj) => ({
...obj,
pinned: true,
}));
posts = [...pinnedPosts, ...this.fetchedData.timeline];
} else {
posts = this.fetchedData.timeline;
}
// Empty container body // Empty container body
this.mtBodyNode.replaceChildren(); this.mtBodyNode.replaceChildren();
// Set posts counter to 0 // Set posts counter to 0
let nbPostShow = 0; let nbPostShow = 0;
for (let i in this.mtSettings.fetchedData.timeline) { for (let i in posts) {
// First filter (Public / Unlisted) // First filter (Public / Unlisted)
if ( if (
this.mtSettings.fetchedData.timeline[i].visibility == "public" || posts[i].visibility == "public" ||
(!this.mtSettings.hideUnlisted && (!this.mtSettings.hideUnlisted && posts[i].visibility == "unlisted")
this.mtSettings.fetchedData.timeline[i].visibility == "unlisted")
) { ) {
// Second filter (Reblog / Replies) // Second filter (Reblog / Replies)
if ( if (
(this.mtSettings.hideReblog && (this.mtSettings.hideReblog && posts[i].reblog) ||
this.mtSettings.fetchedData.timeline[i].reblog) || (this.mtSettings.hideReplies && posts[i].in_reply_to_id)
(this.mtSettings.hideReplies &&
this.mtSettings.fetchedData.timeline[i].in_reply_to_id)
) { ) {
// Nothing here (Don't append posts) // Nothing here (Don't append posts)
} else { } else {
if (nbPostShow < this.mtSettings.maxNbPostShow) { if (nbPostShow < this.mtSettings.maxNbPostShow) {
this.#appendPost( this.#appendPost(posts[i], Number(i));
this.mtSettings.fetchedData.timeline[i],
Number(i)
);
nbPostShow++; nbPostShow++;
} else { } else {
// Nothing here (Reached the limit of maximum number of posts to show) // Nothing here (Reached the limit of maximum number of posts to show)
@ -301,7 +322,7 @@ export class Init {
if (this.mtBodyNode.innerHTML === "") { if (this.mtBodyNode.innerHTML === "") {
const errorMessage = const errorMessage =
"No posts to show <hr/>" + "No posts to show <hr/>" +
(this.mtSettings.fetchedData.timeline?.length || 0) + (posts?.length || 0) +
" posts have been fetched from the server <hr/>This may be due to an incorrect configuration in the parameters or to filters applied (to hide certains type of posts)"; " posts have been fetched from the server <hr/>This may be due to an incorrect configuration in the parameters or to filters applied (to hide certains type of posts)";
this.#showError(errorMessage, "📭"); this.#showError(errorMessage, "📭");
} else { } else {
@ -335,6 +356,7 @@ export class Init {
let avatar, let avatar,
user, user,
userName, userName,
accountName,
url, url,
date, date,
formattedDate, formattedDate,
@ -371,22 +393,34 @@ export class Init {
"</a>"; "</a>";
// User name and url // User name and url
userName = c.reblog.account.display_name if (!this.mtSettings.hideEmojos && c.reblog.account.display_name) {
? c.reblog.account.display_name
: c.reblog.account.username;
if (!this.mtSettings.hideEmojos) {
userName = this.#createEmoji( userName = this.#createEmoji(
userName, c.reblog.account.display_name,
this.mtSettings.fetchedData.emojos this.fetchedData.emojos
); );
} else {
userName = c.reblog.account.display_name;
} }
if (!this.mtSettings.hideUserAccount) {
accountName =
'<br/><span class="mt-post-header-user-account">@' +
c.reblog.account.username +
"@" +
new URL(c.reblog.account.url).hostname +
"</span>";
} else {
accountName = "";
}
user = user =
'<div class="mt-post-header-user">' + '<div class="mt-post-header-user">' +
'<a href="' + '<a href="' +
c.reblog.account.url + c.reblog.account.url +
'" rel="nofollow noopener noreferrer" target="_blank">' + '" rel="nofollow noopener noreferrer" target="_blank"><bdi class="mt-post-header-user-name">' +
userName + userName +
'<span class="visually-hidden"> account</span>' + "</bdi>" +
accountName +
"</a>" + "</a>" +
"</div>"; "</div>";
@ -419,22 +453,34 @@ export class Init {
"</a>"; "</a>";
// User name and url // User name and url
userName = c.account.display_name if (!this.mtSettings.hideEmojos && c.account.display_name) {
? c.account.display_name
: c.account.username;
if (!this.mtSettings.hideEmojos) {
userName = this.#createEmoji( userName = this.#createEmoji(
userName, c.account.display_name,
this.mtSettings.fetchedData.emojos this.fetchedData.emojos
); );
} else {
userName = c.account.display_name;
} }
if (!this.mtSettings.hideUserAccount) {
accountName =
'<br/><span class="mt-post-header-user-account">@' +
c.account.username +
"@" +
new URL(c.account.url).hostname +
"</span>";
} else {
accountName = "";
}
user = user =
'<div class="mt-post-header-user">' + '<div class="mt-post-header-user">' +
'<a href="' + '<a href="' +
c.account.url + c.account.url +
'" rel="nofollow noopener noreferrer" target="_blank">' + '" rel="nofollow noopener noreferrer" target="_blank"><bdi class="mt-post-header-user-name">' +
userName + userName +
'<span class="visually-hidden"> account</span>' + "</bdi>" +
accountName +
"</a>" + "</a>" +
"</div>"; "</div>";
@ -451,6 +497,9 @@ export class Init {
formattedDate = this.#formatDate(date); formattedDate = this.#formatDate(date);
const timestamp = const timestamp =
'<div class="mt-post-header-date">' + '<div class="mt-post-header-date">' +
(c.pinned
? '<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24" class="mt-post-pinned" aria-hidden="true"><path d="m640-480 80 80v80H520v240l-40 40-40-40v-240H240v-80l80-80v-280h-40v-80h400v80h-40v280Zm-286 80h252l-46-46v-314H400v314l-46 46Zm126 0Z"></path></svg>'
: "") +
'<a href="' + '<a href="' +
url + url +
'" rel="nofollow noopener noreferrer" target="_blank">' + '" rel="nofollow noopener noreferrer" target="_blank">' +
@ -459,6 +508,7 @@ export class Init {
'">' + '">' +
formattedDate + formattedDate +
"</time>" + "</time>" +
(c.edited_at ? " *" : "") +
"</a>" + "</a>" +
"</div>"; "</div>";
@ -627,7 +677,7 @@ export class Init {
// Convert emojos shortcode into images // Convert emojos shortcode into images
if (!this.mtSettings.hideEmojos) { if (!this.mtSettings.hideEmojos) {
content = this.#createEmoji(content, this.mtSettings.fetchedData.emojos); content = this.#createEmoji(content, this.fetchedData.emojos);
} }
// Convert markdown styles into HTML // Convert markdown styles into HTML
@ -727,34 +777,17 @@ 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 (MM DD, YYYY) * @returns {string} Date formated
*/ */
#formatDate(d) { #formatDate(d) {
const monthNames = [ const originalDate = new Date(d);
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec",
];
const date = new Date(d); const formattedDate = new Intl.DateTimeFormat(
this.mtSettings.dateLocale,
this.mtSettings.dateOptions
).format(originalDate);
const displayDate = return formattedDate;
monthNames[date.getMonth()] +
" " +
date.getDate() +
", " +
date.getFullYear();
return displayDate;
} }
/** /**