Feature/link preview

This commit is contained in:
i.j 2023-08-14 10:49:52 +00:00
parent 621a597e11
commit 40a5d0a163
6 changed files with 390 additions and 225 deletions

View File

@ -1,3 +1,8 @@
v3.8.1 - 14/08/2023
- Show preview card from link, photo or video URL
- Add description for the ALT attribute in images
- Improve JS comments
v3.8.0 - 04/08/2023 v3.8.0 - 04/08/2023
- Add spoiler/sensitive button for reblog content - Add spoiler/sensitive button for reblog content
@ -77,8 +82,8 @@ v1.5.3 - 23/05/2021
v1.5.2 - 22/05/2021 v1.5.2 - 22/05/2021
- Add avatar name - Add avatar name
- Use id instead of css class for js selectors - Use id instead of css class for JS selectors
- Rearrange functions in js - Rearrange functions in JS
v1.5.1 - 17/05/2021 v1.5.1 - 17/05/2021
- Add ellipses for long messages - Add ellipses for long messages

View File

@ -80,6 +80,9 @@ hide_reblog: false,
// Hide replies toots. Default: don't hide // Hide replies toots. Default: don't hide
hide_replies: false, hide_replies: false,
// Hide preview for links. Default: don't hide
hide_preview_link: false,
// Converts Markdown symbol ">" at the beginning of a paragraph into a blockquote HTML tag (default: don't apply) // Converts Markdown symbol ">" at the beginning of a paragraph into a blockquote HTML tag (default: don't apply)
markdown_blockquote: false, markdown_blockquote: false,

View File

@ -1,4 +1,4 @@
/* Mastodon embed feed timeline v3.8.0 */ /* Mastodon embed feed timeline v3.8.1 */
/* More info at: */ /* More info at: */
/* https://gitlab.com/idotj/mastodon-embed-feed-timeline */ /* https://gitlab.com/idotj/mastodon-embed-feed-timeline */
@ -40,7 +40,7 @@ html[data-theme="dark"] {
text-decoration: none; text-decoration: none;
color: var(--link-color); color: var(--link-color);
} }
.mt-timeline a:hover { .mt-timeline a:not(.toot-preview-link):hover {
text-decoration: underline; text-decoration: underline;
} }
.mt-timeline::-webkit-scrollbar { .mt-timeline::-webkit-scrollbar {
@ -78,7 +78,7 @@ html[data-theme="dark"] {
/* Toot container */ /* Toot container */
.mt-toot { .mt-toot {
margin: 0.25rem; margin: 0.25rem;
padding: 1rem 0.5rem 2rem 4rem; padding: 1rem 0.5rem 1.5rem 4rem;
position: relative; position: relative;
min-height: 3.75rem; min-height: 3.75rem;
background-color: transparent; background-color: transparent;
@ -119,6 +119,7 @@ html[data-theme="dark"] {
.mt-user { .mt-user {
display: table; display: table;
font-weight: 600; font-weight: 600;
margin-bottom: 1rem;
} }
.mt-user > a { .mt-user > a {
color: var(--content-text) !important; color: var(--content-text) !important;
@ -196,7 +197,7 @@ html[data-theme="dark"] {
/* Medias */ /* Medias */
.toot-media { .toot-media {
overflow: hidden; overflow: hidden;
margin-bottom: 0.25rem; margin-bottom: 0.5rem;
} }
.toot-media-preview { .toot-media-preview {
position: relative; position: relative;
@ -239,6 +240,47 @@ html[data-theme="dark"] {
text-align: center; text-align: center;
} }
/* Preview link */
.toot-preview-link {
min-height: 4rem;
display: flex;
flex-direction: row;
border: 1px solid var(--line-gray-color);
border-radius: 0.5rem;
color: var(--link-color);
font-size: 0.8rem;
margin: 1rem 0 0.5rem 0;
overflow: hidden;
}
.toot-preview-image {
width: 40%;
align-self: stretch;
}
.toot-preview-image img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
}
.toot-preview-noImage {
width: 40%;
font-size: 1.5rem;
align-self: center;
text-align: center;
}
.toot-preview-content {
width: 60%;
display: flex;
align-self: center;
flex-direction: column;
padding: 0.5rem 1rem;
gap: 0.5rem;
}
.toot-preview-title {
font-weight: 600;
}
/* Spoiler button */ /* Spoiler button */
.spoiler-link { .spoiler-link {
border-radius: 2px; border-radius: 2px;

View File

@ -1,8 +1,13 @@
// Mastodon embed feed timeline v3.8.0 /**
// More info at: * Mastodon embed feed timeline v3.8.1
// https://gitlab.com/idotj/mastodon-embed-feed-timeline * More info at:
* https://gitlab.com/idotj/mastodon-embed-feed-timeline
*/
// Timeline settings /**
* Timeline settings
* Adjust these parameters to customize your timeline
*/
window.addEventListener("load", () => { window.addEventListener("load", () => {
let mapi = new MastodonApi({ let mapi = new MastodonApi({
// Id of the <div> containing the timeline // Id of the <div> containing the timeline
@ -38,6 +43,9 @@ window.addEventListener("load", () => {
// Hide replies toots. Default: don't hide // Hide replies toots. Default: don't hide
hide_replies: false, hide_replies: false,
// Hide preview card if toot contains a link, photo or video from a URL. Default: don't hide
hide_preview_link: false,
// Converts Markdown symbol ">" at the beginning of a paragraph into a blockquote HTML tag (default: don't apply) // Converts Markdown symbol ">" at the beginning of a paragraph into a blockquote HTML tag (default: don't apply)
markdown_blockquote: false, markdown_blockquote: false,
@ -49,8 +57,13 @@ window.addEventListener("load", () => {
}); });
}); });
let MastodonApi = function (params_) { /**
// Endpoint access settings / default values * Set all variables with customized values or use default ones
* @param {object} params_ User customized values
* Trigger color theme function
* Trigger main function to build the timeline
*/
const MastodonApi = function (params_) {
this.DEFAULT_THEME = params_.default_theme || "auto"; this.DEFAULT_THEME = params_.default_theme || "auto";
this.INSTANCE_URL = params_.instance_url; this.INSTANCE_URL = params_.instance_url;
this.USER_ID = params_.user_id || ""; this.USER_ID = params_.user_id || "";
@ -66,6 +79,10 @@ let MastodonApi = function (params_) {
typeof params_.hide_reblog !== "undefined" ? params_.hide_reblog : false; typeof params_.hide_reblog !== "undefined" ? params_.hide_reblog : false;
this.HIDE_REPLIES = this.HIDE_REPLIES =
typeof params_.hide_replies !== "undefined" ? params_.hide_replies : false; typeof params_.hide_replies !== "undefined" ? params_.hide_replies : false;
this.HIDE_PREVIEW_LINK =
typeof params_.hide_preview_link !== "undefined"
? params_.hide_preview_link
: false;
this.MARKDOWN_BLOCKQUOTE = this.MARKDOWN_BLOCKQUOTE =
typeof params_.markdown_blockquote !== "undefined" typeof params_.markdown_blockquote !== "undefined"
? params_.markdown_blockquote ? params_.markdown_blockquote
@ -73,24 +90,29 @@ let MastodonApi = function (params_) {
this.TEXT_MAX_LINES = params_.text_max_lines || "0"; this.TEXT_MAX_LINES = params_.text_max_lines || "0";
this.LINK_SEE_MORE = params_.link_see_more; this.LINK_SEE_MORE = params_.link_see_more;
// Target selector
this.mtBodyContainer = document.getElementById(params_.container_body_id); this.mtBodyContainer = document.getElementById(params_.container_body_id);
// Apply selected appearance this.setTheme();
this.applyTheme();
// Get the toots this.buildTimeline();
this.getToots();
}; };
// Theme style /**
MastodonApi.prototype.applyTheme = function () { * Set the theme style choosen by user or browser/OS
*/
MastodonApi.prototype.setTheme = function () {
/**
* Set the theme value in the <html> tag using the attribute "data-theme"
* @param {string} theme Type of theme to apply: dark or light
*/
const setTheme = function (theme) { const setTheme = function (theme) {
document.documentElement.setAttribute("data-theme", theme); document.documentElement.setAttribute("data-theme", theme);
}; };
if (this.DEFAULT_THEME === "auto") { if (this.DEFAULT_THEME === "auto") {
let systemTheme = window.matchMedia("(prefers-color-scheme: dark)"); let systemTheme = window.matchMedia("(prefers-color-scheme: dark)");
systemTheme.matches ? setTheme("dark") : setTheme("light"); systemTheme.matches ? setTheme("dark") : setTheme("light");
// Update the theme if user change browser/OS preference
systemTheme.addEventListener("change", (e) => { systemTheme.addEventListener("change", (e) => {
e.matches ? setTheme("dark") : setTheme("light"); e.matches ? setTheme("dark") : setTheme("light");
}); });
@ -99,8 +121,10 @@ MastodonApi.prototype.applyTheme = function () {
} }
}; };
// Listing toots function /**
MastodonApi.prototype.getToots = function () { * Listing toots function
*/
MastodonApi.prototype.buildTimeline = function () {
let mapi = this; let mapi = this;
let requestURL = ""; let requestURL = "";
@ -131,7 +155,6 @@ MastodonApi.prototype.getToots = function () {
// Empty the <div> container // Empty the <div> container
this.mtBodyContainer.innerHTML = ""; this.mtBodyContainer.innerHTML = "";
// Add toots
for (let i in jsonData) { for (let i in jsonData) {
// First filter (Public / Unlisted) // First filter (Public / Unlisted)
if ( if (
@ -146,7 +169,7 @@ MastodonApi.prototype.getToots = function () {
// Nothing here (Don't append toots) // Nothing here (Don't append toots)
} else { } else {
// Format and append toots // Format and append toots
appendToot.call(mapi, jsonData[i], i); appendToot.call(mapi, jsonData[i], Number(i));
} }
} }
} }
@ -192,197 +215,21 @@ MastodonApi.prototype.getToots = function () {
this.mtBodyContainer.setAttribute("role", "none"); this.mtBodyContainer.setAttribute("role", "none");
}); });
// Inner function to add each toot content in container /**
let appendToot = function (status_, index) { * Inner function to add each toot in timeline container
let avatar, user, content, url, date; * @param {object} c Toot content
* @param {number} i Index of toot
if (status_.reblog) { */
// BOOSTED toot const appendToot = function (c, i) {
// Toot url this.mtBodyContainer.insertAdjacentHTML(
url = status_.reblog.url; "beforeend",
this.assambleToot(c, i)
// Boosted avatar );
avatar =
'<a href="' +
status_.reblog.account.url +
'" class="mt-avatar mt-avatar-boosted" style="background-image:url(' +
status_.reblog.account.avatar +
');" rel="nofollow noopener noreferrer" target="_blank">' +
'<div class="mt-avatar mt-avatar-booster" style="background-image:url(' +
status_.account.avatar +
');">' +
"</div>" +
'<span class="visually-hidden">' +
status_.account.username +
" avatar" +
"</span>" +
"</a>";
// User name and url
user =
'<div class="mt-user">' +
'<a href="' +
status_.reblog.account.url +
'" rel="nofollow noopener noreferrer" target="_blank">' +
status_.reblog.account.username +
'<span class="visually-hidden"> post</span>' +
"</a>" +
"</div>";
// Date
date = this.formatDate(status_.reblog.created_at);
} else {
// STANDARD toot
// Toot url
url = status_.url;
// Avatar
avatar =
'<a href="' +
status_.account.url +
'" class="mt-avatar" style="background-image:url(' +
status_.account.avatar +
');" rel="nofollow noopener noreferrer" target="_blank">' +
'<span class="visually-hidden">' +
status_.account.username +
" avatar" +
"</span>" +
"</a>";
// User name and url
user =
'<div class="mt-user">' +
'<a href="' +
status_.account.url +
'" rel="nofollow noopener noreferrer" target="_blank">' +
status_.account.username +
'<span class="visually-hidden"> post</span>' +
"</a>" +
"</div>";
// Date
date = this.formatDate(status_.created_at);
}
// Main text
let text_css = "";
if (this.TEXT_MAX_LINES !== "0") {
text_css = "truncate";
document.documentElement.style.setProperty(
"--text-max-lines",
this.TEXT_MAX_LINES
);
}
if (status_.spoiler_text !== "") {
content =
'<div class="toot-text">' +
status_.spoiler_text +
' <button type="button" class="spoiler-link" aria-expanded="false">Show more</button>' +
'<div class="spoiler-text-hidden">' +
this.formatTootText(status_.content) +
"</div>" +
"</div>";
} else if (
status_.reblog &&
status_.reblog.content !== "" &&
status_.reblog.spoiler_text !== ""
) {
content =
'<div class="toot-text">' +
status_.reblog.spoiler_text +
' <button type="button" class="spoiler-link" aria-expanded="false">Show more</button>' +
'<div class="spoiler-text-hidden">' +
this.formatTootText(status_.reblog.content) +
"</div>" +
"</div>";
} else if (
status_.reblog &&
status_.reblog.content !== "" &&
status_.reblog.spoiler_text === ""
) {
content =
'<div class="toot-text ' +
text_css +
'">' +
"<div>" +
this.formatTootText(status_.reblog.content) +
"</div>" +
"</div>";
} else {
content =
'<div class="toot-text ' +
text_css +
'">' +
"<div>" +
this.formatTootText(status_.content) +
"</div>" +
"</div>";
}
// Media attachments
let media = "";
if (status_.media_attachments.length > 0) {
for (let picid in status_.media_attachments) {
media = this.replaceMedias(
status_.media_attachments[picid],
status_.sensitive
);
}
}
if (status_.reblog && status_.reblog.media_attachments.length > 0) {
for (let picid in status_.reblog.media_attachments) {
media = this.replaceMedias(
status_.reblog.media_attachments[picid],
status_.reblog.sensitive
);
}
}
// Poll
let poll = "";
let pollOption = "";
if (status_.poll) {
for (let i in status_.poll.options) {
pollOption += "<li>" + status_.poll.options[i].title + "</li>";
}
poll =
'<div class="toot-poll">' + "<ul>" + pollOption + "</ul>" + "</div>";
}
// Date
let timestamp =
'<div class="toot-date">' +
'<a href="' +
url +
'" rel="nofollow noopener noreferrer" tabindex="-1" target="_blank">' +
date +
"</a>" +
"</div>";
// Add all to main toot container
let toot =
'<article class="mt-toot" aria-posinset="' +
(Number(index) + 1) +
'" aria-setsize="' +
this.TOOTS_LIMIT +
'" data-location="' +
url +
'" tabindex="0">' +
avatar +
user +
content +
media +
poll +
timestamp +
"</article>";
this.mtBodyContainer.insertAdjacentHTML("beforeend", toot);
}; };
// Toot interactions // Toot interactions
this.mtBodyContainer.addEventListener("click", function (e) { this.mtBodyContainer.addEventListener("click", function (e) {
// Check if clicked in a toot // Check if toot cointainer was clicked
if ( if (
e.target.localName == "article" || e.target.localName == "article" ||
e.target.offsetParent.localName == "article" || e.target.offsetParent.localName == "article" ||
@ -390,7 +237,7 @@ MastodonApi.prototype.getToots = function () {
) { ) {
openTootURL(e); openTootURL(e);
} }
// Check if clicked in Show More/Less button // Check if Show More/Less button was clicked
if ( if (
e.target.localName == "button" && e.target.localName == "button" &&
e.target.className == "spoiler-link" e.target.className == "spoiler-link"
@ -399,26 +246,33 @@ MastodonApi.prototype.getToots = function () {
} }
}); });
this.mtBodyContainer.addEventListener("keydown", function (e) { this.mtBodyContainer.addEventListener("keydown", function (e) {
// Check if Enter key pressed with focus in an article // Check if Enter key was pressed with focus in an article
if (e.key === "Enter" && e.target.localName == "article") { if (e.key === "Enter" && e.target.localName == "article") {
openTootURL(e); openTootURL(e);
} }
}); });
// Open Toot in a new page avoiding any other natural link /**
* Open toot in a new page avoiding any other natural link
* @param {event} e User interaction trigger
*/
const openTootURL = function (e) { const openTootURL = function (e) {
let urlToot = e.target.closest(".mt-toot").dataset.location; let urlToot = e.target.closest(".mt-toot").dataset.location;
if ( if (
e.target.localName !== "a" && e.target.localName !== "a" &&
e.target.localName !== "span" && e.target.localName !== "span" &&
e.target.localName !== "button" && e.target.localName !== "button" &&
e.target.parentNode.className !== "toot-preview-image" &&
urlToot urlToot
) { ) {
window.open(urlToot, "_blank"); window.open(urlToot, "_blank");
} }
}; };
// Spoiler button /**
* Spoiler button
* @param {event} e User interaction trigger
*/
const toogleSpoiler = function (e) { const toogleSpoiler = function (e) {
const nextSibling = e.target.nextSibling; const nextSibling = e.target.nextSibling;
if (nextSibling.localName === "img") { if (nextSibling.localName === "img") {
@ -443,7 +297,206 @@ MastodonApi.prototype.getToots = function () {
}; };
}; };
// Handle text changes made to Toots /**
* Build toot structure
* @param {object} c Toot content
* @param {number} i Index of toot
*/
MastodonApi.prototype.assambleToot = function (c, i) {
let avatar, user, content, url, date;
if (c.reblog) {
// BOOSTED toot
// Toot url
url = c.reblog.url;
// Boosted avatar
avatar =
'<a href="' +
c.reblog.account.url +
'" class="mt-avatar mt-avatar-boosted" style="background-image:url(' +
c.reblog.account.avatar +
');" rel="nofollow noopener noreferrer" target="_blank">' +
'<div class="mt-avatar mt-avatar-booster" style="background-image:url(' +
c.account.avatar +
');">' +
"</div>" +
'<span class="visually-hidden">' +
c.account.username +
" avatar" +
"</span>" +
"</a>";
// User name and url
user =
'<div class="mt-user">' +
'<a href="' +
c.reblog.account.url +
'" rel="nofollow noopener noreferrer" target="_blank">' +
c.reblog.account.username +
'<span class="visually-hidden"> post</span>' +
"</a>" +
"</div>";
// Date
date = this.formatDate(c.reblog.created_at);
} else {
// STANDARD toot
// Toot url
url = c.url;
// Avatar
avatar =
'<a href="' +
c.account.url +
'" class="mt-avatar" style="background-image:url(' +
c.account.avatar +
');" rel="nofollow noopener noreferrer" target="_blank">' +
'<span class="visually-hidden">' +
c.account.username +
" avatar" +
"</span>" +
"</a>";
// User name and url
user =
'<div class="mt-user">' +
'<a href="' +
c.account.url +
'" rel="nofollow noopener noreferrer" target="_blank">' +
c.account.username +
'<span class="visually-hidden"> post</span>' +
"</a>" +
"</div>";
// Date
date = this.formatDate(c.created_at);
}
// Main text
let text_css = "";
if (this.TEXT_MAX_LINES !== "0") {
text_css = "truncate";
document.documentElement.style.setProperty(
"--text-max-lines",
this.TEXT_MAX_LINES
);
}
if (c.spoiler_text !== "") {
content =
'<div class="toot-text">' +
c.spoiler_text +
' <button type="button" class="spoiler-link" aria-expanded="false">Show more</button>' +
'<div class="spoiler-text-hidden">' +
this.formatTootText(c.content) +
"</div>" +
"</div>";
} else if (
c.reblog &&
c.reblog.content !== "" &&
c.reblog.spoiler_text !== ""
) {
content =
'<div class="toot-text">' +
c.reblog.spoiler_text +
' <button type="button" class="spoiler-link" aria-expanded="false">Show more</button>' +
'<div class="spoiler-text-hidden">' +
this.formatTootText(c.reblog.content) +
"</div>" +
"</div>";
} else if (
c.reblog &&
c.reblog.content !== "" &&
c.reblog.spoiler_text === ""
) {
content =
'<div class="toot-text ' +
text_css +
'">' +
"<div>" +
this.formatTootText(c.reblog.content) +
"</div>" +
"</div>";
} else {
content =
'<div class="toot-text ' +
text_css +
'">' +
"<div>" +
this.formatTootText(c.content) +
"</div>" +
"</div>";
}
// Media attachments
let media = "";
if (c.media_attachments.length > 0) {
for (let picid in c.media_attachments) {
media = this.placeMedias(c.media_attachments[picid], c.sensitive);
}
}
if (c.reblog && c.reblog.media_attachments.length > 0) {
for (let picid in c.reblog.media_attachments) {
media = this.placeMedias(
c.reblog.media_attachments[picid],
c.reblog.sensitive
);
}
}
// Preview link
let preview_link = "";
if (!this.HIDE_PREVIEW_LINK && c.card) {
preview_link = this.placePreviewLink(c.card);
}
// Poll
let poll = "";
let pollOption = "";
if (c.poll) {
for (let i in c.poll.options) {
pollOption += "<li>" + c.poll.options[i].title + "</li>";
}
poll = '<div class="toot-poll">' + "<ul>" + pollOption + "</ul>" + "</div>";
}
// Date
const timestamp =
'<div class="toot-date">' +
'<a href="' +
url +
'" rel="nofollow noopener noreferrer" tabindex="-1" target="_blank">' +
date +
"</a>" +
"</div>";
// Add all to main toot container
const toot =
'<article class="mt-toot" aria-posinset="' +
(i + 1) +
'" aria-setsize="' +
this.TOOTS_LIMIT +
'" data-location="' +
url +
'" tabindex="0">' +
avatar +
user +
content +
media +
preview_link +
poll +
timestamp +
"</article>";
return toot;
};
/**
* Handle text changes made to toots
* @param {string} c Text content
* @returns {string} Text content modified
*/
MastodonApi.prototype.formatTootText = function (c) { MastodonApi.prototype.formatTootText = function (c) {
let content = c; let content = c;
@ -464,17 +517,30 @@ MastodonApi.prototype.formatTootText = function (c) {
return content; return content;
}; };
// Add target="_blank" to all #hashtags and @mentions /**
* Add target="_blank" to all #hashtags and @mentions in the toot
* @param {string} c Text content
* @returns {string} Text content modified
*/
MastodonApi.prototype.addTarget2hashtagMention = function (c) { MastodonApi.prototype.addTarget2hashtagMention = function (c) {
let content = c.replaceAll('rel="tag"', 'rel="tag" target="_blank"'); let content = c.replaceAll('rel="tag"', 'rel="tag" target="_blank"');
content = content.replaceAll( content = content.replaceAll(
'class="u-url mention"', 'class="u-url mention"',
'class="u-url mention" target="_blank"' 'class="u-url mention" target="_blank"'
); );
return content; return content;
}; };
// Find all start/end <tags> and replace them by another start/end <tags> /**
* Find all start/end <tags> and replace them by another start/end <tags>
* @param {string} c Text content
* @param {string} initialTagOpen Start HTML tag to replace
* @param {string} initialTagClose End HTML tag to replace
* @param {string} replacedTagOpen New start HTML tag
* @param {string} replacedTagClose New end HTML tag
* @returns {string} Text in HTML format
*/
MastodonApi.prototype.replaceHTMLtag = function ( MastodonApi.prototype.replaceHTMLtag = function (
c, c,
initialTagOpen, initialTagOpen,
@ -484,14 +550,20 @@ MastodonApi.prototype.replaceHTMLtag = function (
) { ) {
if (c.includes(initialTagOpen)) { if (c.includes(initialTagOpen)) {
const regex = new RegExp(initialTagOpen + "(.*?)" + initialTagClose, "gi"); const regex = new RegExp(initialTagOpen + "(.*?)" + initialTagClose, "gi");
return c.replace(regex, replacedTagOpen + "$1" + replacedTagClose); return c.replace(regex, replacedTagOpen + "$1" + replacedTagClose);
} else { } else {
return c; return c;
} }
}; };
// Place media /**
MastodonApi.prototype.replaceMedias = function (m, s) { * Place media
* @param {object} m Media content
* @param {boolean} s Spoiler/Sensitive status
* @returns {string} Media in HTML format
*/
MastodonApi.prototype.placeMedias = function (m, s) {
let spoiler = s || false; let spoiler = s || false;
const pic = const pic =
'<div class="toot-media ' + '<div class="toot-media ' +
@ -500,13 +572,51 @@ MastodonApi.prototype.replaceMedias = function (m, s) {
(spoiler ? '<button class="spoiler-link">Show content</button>' : "") + (spoiler ? '<button class="spoiler-link">Show content</button>' : "") +
'<img onload="removeSpinner(this)" onerror="removeSpinner(this)" src="' + '<img onload="removeSpinner(this)" onerror="removeSpinner(this)" src="' +
m.preview_url + m.preview_url +
'" alt="" loading="lazy" />' + '" alt="' +
(m.description ? m.description : "") +
'" loading="lazy" />' +
"</div>"; "</div>";
return pic; return pic;
}; };
// Format date /**
* Place preview link
* @param {object} c Preview link content
* @returns {string} Preview link in HTML format
*/
MastodonApi.prototype.placePreviewLink = function (c) {
let card =
'<a href="' +
c.url +
'" class="toot-preview-link" target="_blank" rel="noopener noreferrer">' +
(c.image
? '<div class="toot-preview-image"><img onload="removeSpinner(this)" onerror="removeSpinner(this)" src="' +
c.image +
'" alt="" loading="lazy" /></div>'
: '<div class="toot-preview-noImage">📄</div>') +
"</div>" +
'<div class="toot-preview-content">' +
(c.provider_name
? '<span class="toot-preview-provider">' + c.provider_name + "</span>"
: "") +
'<span class="toot-preview-title">' +
c.title +
"</span>" +
(c.author_name
? '<span class="toot-preview-author">By ' + c.author_name + "</span>"
: "") +
"</div>" +
"</a>";
return card;
};
/**
* Format date
* @param {string} d Date in ISO format (YYYY-MM-DDTHH:mm:ss.sssZ)
* @returns {string} Date formated (MM DD, YYYY)
*/
MastodonApi.prototype.formatDate = function (d) { MastodonApi.prototype.formatDate = function (d) {
const monthNames = [ const monthNames = [
"Jan", "Jan",
@ -535,11 +645,16 @@ MastodonApi.prototype.formatDate = function (d) {
return displayDate; return displayDate;
}; };
// Loading spinner /**
* Loading spinner
* @param {object} e Image containing the spinner
*/
const removeSpinner = function (e) { const removeSpinner = function (e) {
const spinnerCSS = "loading-spinner"; const spinnerCSS = "loading-spinner";
// Find closest parent container (1st, 2nd or 3rd level) // Find closest parent container (1st, 2nd or 3rd level)
let spinnerContainer = e.closest("." + spinnerCSS); let spinnerContainer = e.closest("." + spinnerCSS);
if (spinnerContainer) { if (spinnerContainer) {
spinnerContainer.classList.remove(spinnerCSS); spinnerContainer.classList.remove(spinnerCSS);
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long