Feature/link preview
This commit is contained in:
		
							parent
							
								
									621a597e11
								
							
						
					
					
						commit
						40a5d0a163
					
				| @ -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 | ||||||
|  | |||||||
| @ -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, | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -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; | ||||||
|  | |||||||
| @ -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); | ||||||
|   } |   } | ||||||
|  | |||||||
							
								
								
									
										2
									
								
								src/mastodon-timeline.min.css
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								src/mastodon-timeline.min.css
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										2
									
								
								src/mastodon-timeline.min.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								src/mastodon-timeline.min.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 i.j
						i.j