From 2cbfb4472dc77f34034f2c0758c03eb152b90d63 Mon Sep 17 00:00:00 2001 From: "i.j" Date: Thu, 4 Apr 2024 11:33:44 +0000 Subject: [PATCH] Feature/sanitize content --- .gitlab-ci.yml | 17 --- CHANGELOG | 6 + README.md | 11 +- dist/mastodon-timeline.esm.js | 4 +- dist/mastodon-timeline.umd.js | 4 +- package-lock.json | 144 ++++++++++++++---------- package.json | 7 +- src/mastodon-timeline.css | 2 +- src/mastodon-timeline.js | 203 ++++++++++++++++++++++++---------- 9 files changed, 252 insertions(+), 146 deletions(-) delete mode 100644 .gitlab-ci.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index 24fdf99..0000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,17 +0,0 @@ -image: node:latest - -stages: - - deploy - -variables: - REGISTRY_URL: "//${CI_SERVER_HOST}/api/v4/projects/${CI_PROJECT_ID}/packages/npm/" - -deploy: - stage: deploy - script: - - echo "@scope:registry=https:${REGISTRY_URL}" > .npmrc - - echo "${REGISTRY_URL}:_authToken=${CI_JOB_TOKEN}" >> .npmrc - - npm publish - only: - - master - environment: production diff --git a/CHANGELOG b/CHANGELOG index a77726f..50d9bb8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,9 @@ +v4.4.1 - 04/04/2024 +- Fix render emojos in warning/spoiler text +- Sanitize post content before rendering +- Add custom title for play video button +- Update Rollup devDependency version + v4.3.12 - 26/03/2024 - Add button to hide sensitive/spoiler media - Fix Refresh button bug when empty text diff --git a/README.md b/README.md index b498bd0..ca5bb1e 100644 --- a/README.md +++ b/README.md @@ -65,11 +65,11 @@ This option allows you to start without the need to upload any files on your ser Copy the following CSS and JS links to include them in your project: ```html - + ``` ```html - + ``` ### Package manager @@ -273,13 +273,16 @@ Here you have all the options available to quickly setup and customize your time // Default: false (don't hide) hideEmojos: false, - // Customize the text of the button used for showing sensitive/spoiler media content + // Customize the text of the button used for showing a sensitive/spoiler media content btnShowContent: "SHOW CONTENT", - // Hide video image preview and load video player instead + // Hide video image preview and load the video player instead // Default: false (don't hide) hideVideoPreview: false, + // Customize the text of the button used for the image preview to play the video + btnPlayVideoTxt: "Load and play video", + // Hide preview card if post contains a link, photo or video from a Url // Default: false (don't hide) hidePreviewLink: false, diff --git a/dist/mastodon-timeline.esm.js b/dist/mastodon-timeline.esm.js index a79af25..eec777d 100644 --- a/dist/mastodon-timeline.esm.js +++ b/dist/mastodon-timeline.esm.js @@ -1,8 +1,8 @@ /** * Mastodon embed timeline * @author idotj - * @version 4.3.12 + * @version 4.4.1 * @url https://gitlab.com/idotj/mastodon-embed-timeline * @license GNU AGPLv3 */ -class t{constructor(t={}){this.defaultSettings={mtContainerId:"mt-container",instanceUrl:"https://mastodon.social",timelineType:"local",userId:"",profileName:"",hashtagName:"",spinnerClass:"mt-loading-spinner",defaultTheme:"auto",maxNbPostFetch:"20",maxNbPostShow:"20",dateLocale:"en-GB",dateOptions:{day:"2-digit",month:"short",year:"numeric"},hideUnlisted:!1,hideReblog:!1,hideReplies:!1,hidePinnedPosts:!1,hideUserAccount:!1,txtMaxLines:"",btnShowMore:"SHOW MORE",btnShowLess:"SHOW LESS",markdownBlockquote:!1,hideEmojos:!1,btnShowContent:"SHOW CONTENT",hideVideoPreview:!1,hidePreviewLink:!1,previewMaxLines:"",hideCounterBar:!1,disableCarousel:!1,carouselCloseTxt:"Close carousel",carouselPrevTxt:"Previous media item",carouselNextTxt:"Next media item",btnSeeMore:"See more posts at Mastodon",btnReload:"Refresh",insistSearchContainer:!1,insistSearchContainerTime:"3000"},this.mtSettings={...this.defaultSettings,...t},this.#t(),this.linkHeader={},this.mtContainerNode="",this.mtBodyNode="",this.fetchedData={},this.#e((()=>{this.#i()}))}#t(){Number(this.mtSettings.maxNbPostShow)>Number(this.mtSettings.maxNbPostFetch)&&(console.error(`Please check your settings! The maximum number of posts to show is bigger than the maximum number of posts to fetch. Changing the value of "maxNbPostFetch" to: ${this.mtSettings.maxNbPostShow}`),this.mtSettings.maxNbPostFetch=this.mtSettings.maxNbPostShow)}#e(t){"undefined"!=typeof document&&"complete"===document.readyState?t():"undefined"!=typeof document&&"complete"!==document.readyState&&document.addEventListener("DOMContentLoaded",t())}#i(){const t=()=>{this.mtContainerNode=document.getElementById(this.mtSettings.mtContainerId),this.mtBodyNode=this.mtContainerNode.getElementsByClassName("mt-body")[0],this.#s(),this.#a("newTimeline")};if(this.mtSettings.insistSearchContainer){const e=performance.now(),i=()=>{if(document.getElementById(this.mtSettings.mtContainerId))t();else{performance.now()-e container with id: "${this.mtSettings.mtContainerId}" after several attempts for ${this.mtSettings.insistSearchContainerTime/1e3} seconds`)}};i()}else document.getElementById(this.mtSettings.mtContainerId)?t():console.error(`Impossible to find the
container with id: "${this.mtSettings.mtContainerId}". Please try to add the option 'insistSearchContainer: true' when initializing the script`)}mtUpdate(){this.#e((()=>{this.mtBodyNode.replaceChildren(),this.mtBodyNode.insertAdjacentHTML("afterbegin",'
'),this.#a("updateTimeline")}))}mtColorTheme(t){this.#e((()=>{this.mtContainerNode.setAttribute("data-theme",t)}))}#s(){if("auto"===this.mtSettings.defaultTheme){let t=window.matchMedia("(prefers-color-scheme: dark)");t.matches?this.mtColorTheme("dark"):this.mtColorTheme("light"),t.addEventListener("change",(t=>{t.matches?this.mtColorTheme("dark"):this.mtColorTheme("light")}))}else this.mtColorTheme(this.mtSettings.defaultTheme)}#o(){return new Promise(((t,e)=>{const i=this.mtSettings.instanceUrl?`${this.mtSettings.instanceUrl}/api/v1/`:this.#n("Please check your instanceUrl value","⚠️"),s=this.#r(i),a=Object.entries(s).map((([t,i])=>{const s="timeline"===t;return this.#l(i,s).then((e=>({[t]:e}))).catch((s=>(e(new Error(`Something went wrong fetching data from: ${i}`)),this.#n(s.message),{[t]:[]})))}));Promise.all(a).then((async e=>{if(this.fetchedData=e.reduce(((t,e)=>({...t,...e})),{}),!this.mtSettings.hidePinnedPosts&&void 0!==this.fetchedData.pinned?.length&&0!==this.fetchedData.pinned.length){const t=this.fetchedData.pinned.map((t=>({...t,pinned:!0})));this.fetchedData.timeline=[...t,...this.fetchedData.timeline]}if(this.#d())t();else{do{await this.#m()}while(!this.#d()&&this.linkHeader.next);t()}}))}))}#r(t){let e={};return"profile"===this.mtSettings.timelineType?this.mtSettings.userId?(e.timeline=`${t}accounts/${this.mtSettings.userId}/statuses?limit=${this.mtSettings.maxNbPostFetch}`,this.mtSettings.hidePinnedPosts||(e.pinned=`${t}accounts/${this.mtSettings.userId}/statuses?pinned=true`)):this.#n("Please check your userId value","⚠️"):"hashtag"===this.mtSettings.timelineType?this.mtSettings.hashtagName?e.timeline=`${t}timelines/tag/${this.mtSettings.hashtagName}?limit=${this.mtSettings.maxNbPostFetch}`:this.#n("Please check your hashtagName value","⚠️"):"local"===this.mtSettings.timelineType?e.timeline=`${t}timelines/public?local=true&limit=${this.mtSettings.maxNbPostFetch}`:this.#n("Please check your timelineType value","⚠️"),this.mtSettings.hideEmojos||(e.emojos=`${t}custom_emojis`),e}async#l(t,e=!1){const i=await fetch(t);if(!i.ok)throw new Error(`\n Failed to fetch the following Url:
${t}
Error status: ${i.status}
Error message: ${i.statusText}\n `);const s=await i.json();return e&&i.headers.get("Link")&&(this.linkHeader=this.#h(i.headers.get("Link"))),s}#d(){return this.fetchedData.timeline.length>=Number(this.mtSettings.maxNbPostFetch)}#m(){return new Promise((t=>{this.linkHeader.next?this.#l(this.linkHeader.next,!0).then((e=>{this.fetchedData.timeline=[...this.fetchedData.timeline,...e],t()})):t()}))}#h(t){const e=t.split(", ").map((t=>t.split("; "))).map((t=>[t[1].replace(/"/g,"").replace("rel=",""),t[0].slice(1,-1)]));return Object.fromEntries(e)}async#a(t){await this.#o();const e=this.fetchedData.timeline;let i=0;if(this.mtBodyNode.replaceChildren(),e.forEach((t=>{const e="public"===t.visibility||!this.mtSettings.hideUnlisted&&"unlisted"===t.visibility,s=this.mtSettings.hideReblog&&t.reblog,a=this.mtSettings.hideReplies&&t.in_reply_to_id;!e||s||a||i${e?.length||0} posts have been fetched from the server
This may be due to an incorrect configuration with the parameters or with the filters applied (to hide certains type of posts)`;this.#n(t,"📭")}}#g(){"0"!==this.mtSettings.txtMaxLines&&0!==this.mtSettings.txtMaxLines.length&&this.mtBodyNode.parentNode.style.setProperty("--mt-txt-max-lines",this.mtSettings.txtMaxLines),"0"!==this.mtSettings.previewMaxLines&&0!==this.mtSettings.previewMaxLines.length&&this.mtBodyNode.parentNode.style.setProperty("--mt-preview-max-lines",this.mtSettings.previewMaxLines)}#u(t){const e=this.mtBodyNode.getElementsByTagName("article");for(let i=0;i
'+this.#f(t.reblog.account.username)+' avatar
'+this.#f(t.account.username)+' avatar
',a=!this.mtSettings.hideEmojos&&t.reblog.account.display_name?this.#S(t.reblog.account.display_name,t.reblog.account.emojis):t.reblog.account.display_name?t.reblog.account.display_name:t.reblog.account.username,o=this.mtSettings.hideUserAccount?"":'
",s='
'+a+""+o+"
",r=t.reblog.created_at,h=t.reblog.replies_count,m=t.reblog.reblogs_count,d=t.reblog.favourites_count):(n=t.url,i='
'+this.#f(t.account.username)+' avatar
',a=!this.mtSettings.hideEmojos&&t.account.display_name?this.#S(t.account.display_name,t.account.emojis):t.account.display_name?t.account.display_name:t.account.username,o=this.mtSettings.hideUserAccount?"":'
",s='
'+a+""+o+"
",r=t.created_at,h=t.replies_count,m=t.reblogs_count,d=t.favourites_count),l=this.#x(r);const c=`\n
\n ${t.pinned?'':""}\n \n \n ${t.edited_at?" *":""}\n \n
`;let p="";if("0"!==this.mtSettings.txtMaxLines){const e=0!==this.mtSettings.txtMaxLines.length?" truncate":"";p=""!==t.spoiler_text?'
'+t.spoiler_text+'
'+this.#y(t.content)+"
":t.reblog&&""!==t.reblog.content&&""!==t.reblog.spoiler_text?'
'+t.reblog.spoiler_text+'
'+this.#y(t.reblog.content)+"
":t.reblog&&""!==t.reblog.content&&""===t.reblog.spoiler_text?'
'+this.#y(t.reblog.content)+"
":'
'+this.#y(t.content)+"
"}let g=[];if(t.media_attachments.length>0)for(let e in t.media_attachments)g.push(this.#L(t.media_attachments[e],t.sensitive));if(t.reblog&&t.reblog.media_attachments.length>0)for(let e in t.reblog.media_attachments)g.push(this.#L(t.reblog.media_attachments[e],t.reblog.sensitive));g=`
${g.join("")}
`;let u="";!this.mtSettings.hidePreviewLink&&t.card&&(u=this.#N(t.card));let v="";if(t.poll){let e="";for(let i in t.poll.options)e+="
  • "+t.poll.options[i].title+"
  • ";v='
      '+e+"
    "}let b="";if(!this.mtSettings.hideCounterBar){b='
    '+('
    '+h+"
    ")+('
    '+m+"
    ")+('
    '+d+"
    ")+"
    "}return'
    '+i+s+c+"
    "+p+g+u+v+b+"
    "}#y(t){let e=t;return e=this.#T(e),this.mtSettings.hideEmojos||(e=this.#S(e,this.fetchedData.emojos)),this.mtSettings.markdownBlockquote&&(e=this.#M(e,"

    >","

    ","

    ","

    ")),e}#T(t){let e=t.replaceAll('rel="tag"','rel="tag" target="_blank"');return e=e.replaceAll('class="u-url mention"','class="u-url mention" target="_blank"'),e}#M(t,e,i,s,a){if(t.includes(e)){const o=new RegExp(e+"(.*?)"+i,"gi");return t.replace(o,s+"$1"+a)}return t}#f(t){return(t??"").replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'")}#S(t,e){if(t.includes(":")){for(const i of e){const e=new RegExp(`\\:${i.shortcode}\\:`,"g");t=t.replace(e,`Emoji ${i.shortcode}`)}return t}return t}#x(t){const e=new Date(t);return new Intl.DateTimeFormat(this.mtSettings.dateLocale,this.mtSettings.dateOptions).format(e)}#L(t,e=!1){const i=t.type,s=e;let a="";const o='";return"image"===i&&(a='
    '+(s?o:"")+''+(t.description?this.#f(t.description):
    '),"audio"===i&&(a=t.preview_url?'
    '+(s?o:"")+''+(t.description?this.#f(t.description):
    ':'
    '+(s?o:"")+'
    '),"video"!==i&&"gifv"!==i||(a=this.mtSettings.hideVideoPreview?'
    '+(s?o:"")+'
    ':'
    '+(s?o:"")+''+(t.description?this.#f(t.description):
    '),a}#C(t,e){let i=document.createElement("dialog");i.id=t,i.classList.add("mt-dialog"),i.dataset.theme=this.mtContainerNode.getAttribute("data-theme"),i.innerHTML=e,document.body.prepend(i),i.showModal(),i.addEventListener("close",(()=>{document.body.removeChild(i)}))}#P(t){const e=Array.from(t.target.parentNode.parentNode.children).filter((t=>!t.classList.contains("mt-post-media-spoiler"))),i=e.indexOf(t.target.parentNode)+1;let s=[];e.forEach(((t,e)=>{let i="";i="gifv"===t.getAttribute("data-media-type")||"video"===t.getAttribute("data-media-type")?`\n \n `:`\n ${t.getAttribute(\n `;const a=`\n \n `;s.push(a)}));const a=`\n \n\n \n\n \n\n \n `;this.#C("mt-carousel",a),s.length>=2&&this.#E(e.length,i)}#E(t,e){let i=e;const s=document.getElementById("mt-carousel-scroll");let a=0,o=!1;const n=document.getElementById("mt-carousel-prev"),r=document.getElementById("mt-carousel-next"),l=(t,e="smooth")=>{document.getElementById("mt-carousel-"+t).scrollIntoView({behavior:e})};l(i,"instant");const d=()=>{clearTimeout(a),a=setTimeout((()=>{o&&(i=(()=>{const t=(s.scrollLeft+s.clientWidth)/s.clientWidth;return Math.round(t+Number.EPSILON)})(),m()),o=!0}),60)};s.addEventListener("scroll",d);const m=()=>{n.hidden=1===i,r.hidden=i===t},h=e=>{const s=e.target.closest("button")?.id;"mt-carousel-next"===s?(o=!1,++i,i>t&&(i=t),l(i),m()):"mt-carousel-prev"===s&&(o=!1,--i,i<1&&(i=1),l(i),m()),"mt-carousel-close"===s&&p()};document.addEventListener("click",h);const c=t=>{"Escape"!==t.key&&27!==t.keyCode||p()};document.addEventListener("keydown",c);const p=()=>{s.removeEventListener("scroll",d),document.removeEventListener("click",h),document.removeEventListener("keydown",c)}}#k(t){const e=t.target.closest("[data-media-type]"),i=e.dataset.mediaUrlHd;e.replaceChildren(),e.innerHTML=``}#$(t){const e=t.target,i=e.nextSibling;e.textContent==this.mtSettings.btnShowMore?(i.classList.remove("spoiler-txt-hidden"),i.classList.add("spoiler-txt-visible"),e.setAttribute("aria-expanded","true"),e.textContent=this.mtSettings.btnShowLess):(i.classList.remove("spoiler-txt-visible"),i.classList.add("spoiler-txt-hidden"),e.setAttribute("aria-expanded","false"),e.textContent=this.mtSettings.btnShowMore)}#_(t){const e=t.target;e.classList.contains("mt-btn-spoiler-media-show")?e.parentNode.classList.remove("mt-post-media-spoiler"):e.parentNode.classList.add("mt-post-media-spoiler")}#N(t){let e="";if("0"!==this.mtSettings.previewMaxLines&&t.description){e=''+this.#A(t.description)+""}return''+(t.image?'
    '+this.#f(t.image_description)+'
    ':'
    📄
    ')+'
    '+(t.provider_name?''+this.#A(t.provider_name)+"":"")+''+t.title+""+e+(t.author_name?''+this.#A(t.author_name)+"":"")+"
    "}#A(t){return(new DOMParser).parseFromString(t,"text/html").body.textContent}#b(){let t="",e="";if(this.mtSettings.btnSeeMore){let e="";"profile"===this.mtSettings.timelineType?this.mtSettings.profileName?e=this.mtSettings.profileName:this.#n("Please check your profileName value","⚠️"):"hashtag"===this.mtSettings.timelineType?e="tags/"+this.mtSettings.hashtagName:"local"===this.mtSettings.timelineType&&(e="public/local"),t=`\n \n ${this.mtSettings.btnSeeMore}\n `}if(this.mtSettings.btnReload&&(e=`\n `),this.mtBodyNode.parentNode.insertAdjacentHTML("beforeend",'"),this.mtSettings.btnReload){this.mtContainerNode.getElementsByClassName("btn-refresh")[0].addEventListener("click",(()=>{this.mtUpdate()}))}}#v(){this.mtBodyNode.addEventListener("click",(t=>{const e=t.target,i=e.localName,s=e.parentNode;("article"==i||"article"==e.offsetParent?.localName||this.mtSettings.disableCarousel&&"image"===s.getAttribute("data-media-type"))&&this.#H(t),e.classList.contains("mt-btn-spoiler-txt")&&this.#$(t),e.classList.contains("mt-btn-spoiler-media")&&this.#_(t),this.mtSettings.disableCarousel||"img"!=i||"image"!==s.getAttribute("data-media-type")&&"audio"!==s.getAttribute("data-media-type")||this.#P(t),("mt-btn-play"==e.className||"svg"==i&&"mt-btn-play"==s.className||"path"==i&&"mt-btn-play"==s.parentNode.className||"img"==i&&("video"===s.getAttribute("data-media-type")||"gifv"===s.getAttribute("data-media-type")))&&this.#k(t)})),this.mtBodyNode.addEventListener("keydown",(t=>{const e=t.target.localName;"Enter"===t.key&&"article"==e&&this.#H(t)}))}#H(t){const e=t.target.closest(".mt-post").dataset.location;"a"!==t.target.localName&&"span"!==t.target.localName&&"button"!==t.target.localName&&"bdi"!==t.target.localName&&"time"!==t.target.localName&&!t.target.classList.contains("mt-post-media-spoiler")&&"mt-post-preview-noImage"!==t.target.className&&"mt-post-avatar-image-big"!==t.target.parentNode.className&&"mt-post-avatar-image-small"!==t.target.parentNode.className&&"mt-post-header-user-name"!==t.target.parentNode.className&&"mt-post-preview-image"!==t.target.parentNode.className&&"mt-post-preview"!==t.target.parentNode.className&&e&&window.open(e,"_blank","noopener")}#p(){const t=e=>{e.target.parentNode.classList.remove(this.mtSettings.spinnerClass),e.target.removeEventListener("load",t),e.target.removeEventListener("error",t)};this.mtBodyNode.querySelectorAll(`.${this.mtSettings.spinnerClass} > img`).forEach((e=>{e.addEventListener("load",t),e.addEventListener("error",t)}))}#n(t,e){const i=e||"❌";throw this.mtBodyNode.innerHTML=`\n
    \n ${i}\n Oops, something's happened:\n
    ${t}
    \n
    `,this.mtBodyNode.setAttribute("role","none"),new Error("Stopping the script due to an error building the timeline.")}}export{t as Init}; +class t{constructor(t={}){this.defaultSettings={mtContainerId:"mt-container",instanceUrl:"https://mastodon.social",timelineType:"local",userId:"",profileName:"",hashtagName:"",spinnerClass:"mt-loading-spinner",defaultTheme:"auto",maxNbPostFetch:"20",maxNbPostShow:"20",dateLocale:"en-GB",dateOptions:{day:"2-digit",month:"short",year:"numeric"},hideUnlisted:!1,hideReblog:!1,hideReplies:!1,hidePinnedPosts:!1,hideUserAccount:!1,txtMaxLines:"",btnShowMore:"SHOW MORE",btnShowLess:"SHOW LESS",markdownBlockquote:!1,hideEmojos:!1,btnShowContent:"SHOW CONTENT",hideVideoPreview:!1,btnPlayVideoTxt:"Load and play video",hidePreviewLink:!1,previewMaxLines:"",hideCounterBar:!1,disableCarousel:!1,carouselCloseTxt:"Close carousel",carouselPrevTxt:"Previous media item",carouselNextTxt:"Next media item",btnSeeMore:"See more posts at Mastodon",btnReload:"Refresh",insistSearchContainer:!1,insistSearchContainerTime:"3000"},this.mtSettings={...this.defaultSettings,...t},this.#t(),this.linkHeader={},this.mtContainerNode="",this.mtBodyNode="",this.fetchedData={},this.#e((()=>{this.#i()}))}#t(){Number(this.mtSettings.maxNbPostShow)>Number(this.mtSettings.maxNbPostFetch)&&(console.error(`Please check your settings! The maximum number of posts to show is bigger than the maximum number of posts to fetch. Changing the value of "maxNbPostFetch" to: ${this.mtSettings.maxNbPostShow}`),this.mtSettings.maxNbPostFetch=this.mtSettings.maxNbPostShow)}#e(t){"undefined"!=typeof document&&"complete"===document.readyState?t():"undefined"!=typeof document&&"complete"!==document.readyState&&document.addEventListener("DOMContentLoaded",t())}#i(){const t=()=>{this.mtContainerNode=document.getElementById(this.mtSettings.mtContainerId),this.mtBodyNode=this.mtContainerNode.getElementsByClassName("mt-body")[0],this.#s(),this.#a("newTimeline")};if(this.mtSettings.insistSearchContainer){const e=performance.now(),i=()=>{if(document.getElementById(this.mtSettings.mtContainerId))t();else{performance.now()-e container with id: "${this.mtSettings.mtContainerId}" after several attempts for ${this.mtSettings.insistSearchContainerTime/1e3} seconds`)}};i()}else document.getElementById(this.mtSettings.mtContainerId)?t():console.error(`Impossible to find the
    container with id: "${this.mtSettings.mtContainerId}". Please try to add the option 'insistSearchContainer: true' when initializing the script`)}mtUpdate(){this.#e((()=>{this.mtBodyNode.replaceChildren(),this.mtBodyNode.insertAdjacentHTML("afterbegin",'
    '),this.#a("updateTimeline")}))}mtColorTheme(t){this.#e((()=>{this.mtContainerNode.setAttribute("data-theme",t)}))}#s(){if("auto"===this.mtSettings.defaultTheme){let t=window.matchMedia("(prefers-color-scheme: dark)");t.matches?this.mtColorTheme("dark"):this.mtColorTheme("light"),t.addEventListener("change",(t=>{t.matches?this.mtColorTheme("dark"):this.mtColorTheme("light")}))}else this.mtColorTheme(this.mtSettings.defaultTheme)}#o(){return new Promise(((t,e)=>{const i=this.mtSettings.instanceUrl?`${this.mtSettings.instanceUrl}/api/v1/`:this.#n("Please check your instanceUrl value","⚠️"),s=this.#r(i),a=Object.entries(s).map((([t,i])=>{const s="timeline"===t;return this.#l(i,s).then((e=>({[t]:e}))).catch((s=>(e(new Error(`Something went wrong fetching data from: ${i}`)),this.#n(s.message),{[t]:[]})))}));Promise.all(a).then((async e=>{if(this.fetchedData=e.reduce(((t,e)=>({...t,...e})),{}),!this.mtSettings.hidePinnedPosts&&void 0!==this.fetchedData.pinned?.length&&0!==this.fetchedData.pinned.length){const t=this.fetchedData.pinned.map((t=>({...t,pinned:!0})));this.fetchedData.timeline=[...t,...this.fetchedData.timeline]}if(this.#d())t();else{do{await this.#m()}while(!this.#d()&&this.linkHeader.next);t()}}))}))}#r(t){let e={};return"profile"===this.mtSettings.timelineType?this.mtSettings.userId?(e.timeline=`${t}accounts/${this.mtSettings.userId}/statuses?limit=${this.mtSettings.maxNbPostFetch}`,this.mtSettings.hidePinnedPosts||(e.pinned=`${t}accounts/${this.mtSettings.userId}/statuses?pinned=true`)):this.#n("Please check your userId value","⚠️"):"hashtag"===this.mtSettings.timelineType?this.mtSettings.hashtagName?e.timeline=`${t}timelines/tag/${this.mtSettings.hashtagName}?limit=${this.mtSettings.maxNbPostFetch}`:this.#n("Please check your hashtagName value","⚠️"):"local"===this.mtSettings.timelineType?e.timeline=`${t}timelines/public?local=true&limit=${this.mtSettings.maxNbPostFetch}`:this.#n("Please check your timelineType value","⚠️"),this.mtSettings.hideEmojos||(e.emojos=`${t}custom_emojis`),e}async#l(t,e=!1){const i=await fetch(t);if(!i.ok)throw new Error(`\n Failed to fetch the following Url:
    ${t}
    Error status: ${i.status}
    Error message: ${i.statusText}\n `);const s=await i.json();return e&&i.headers.get("Link")&&(this.linkHeader=this.#h(i.headers.get("Link"))),s}#d(){return this.fetchedData.timeline.length>=Number(this.mtSettings.maxNbPostFetch)}#m(){return new Promise((t=>{this.linkHeader.next?this.#l(this.linkHeader.next,!0).then((e=>{this.fetchedData.timeline=[...this.fetchedData.timeline,...e],t()})):t()}))}#h(t){const e=t.split(", ").map((t=>t.split("; "))).map((t=>[t[1].replace(/"/g,"").replace("rel=",""),t[0].slice(1,-1)]));return Object.fromEntries(e)}async#a(t){await this.#o();const e=this.fetchedData.timeline;let i=0;if(this.mtBodyNode.replaceChildren(),e.forEach((t=>{const e="public"===t.visibility||!this.mtSettings.hideUnlisted&&"unlisted"===t.visibility,s=this.mtSettings.hideReblog&&t.reblog,a=this.mtSettings.hideReplies&&t.in_reply_to_id;!e||s||a||i${e?.length||0} posts have been fetched from the server
    This may be due to an incorrect configuration with the parameters or with the filters applied (to hide certains type of posts)`;this.#n(t,"📭")}}#g(){"0"!==this.mtSettings.txtMaxLines&&0!==this.mtSettings.txtMaxLines.length&&this.mtBodyNode.parentNode.style.setProperty("--mt-txt-max-lines",this.mtSettings.txtMaxLines),"0"!==this.mtSettings.previewMaxLines&&0!==this.mtSettings.previewMaxLines.length&&this.mtBodyNode.parentNode.style.setProperty("--mt-preview-max-lines",this.mtSettings.previewMaxLines)}#u(t){const e=this.mtBodyNode.getElementsByTagName("article");for(let i=0;i
    '+this.#f(t.reblog.account.username)+' avatar
    '+this.#f(t.account.username)+' avatar
    ',a=!this.mtSettings.hideEmojos&&t.reblog.account.display_name?this.#S(t.reblog.account.display_name,t.reblog.account.emojis):t.reblog.account.display_name?t.reblog.account.display_name:t.reblog.account.username,o=this.mtSettings.hideUserAccount?"":'
    ",s='
    '+a+""+o+"
    ",r=t.reblog.created_at,h=t.reblog.replies_count,m=t.reblog.reblogs_count,d=t.reblog.favourites_count):(n=t.url,i='
    '+this.#f(t.account.username)+' avatar
    ',a=!this.mtSettings.hideEmojos&&t.account.display_name?this.#S(t.account.display_name,t.account.emojis):t.account.display_name?t.account.display_name:t.account.username,o=this.mtSettings.hideUserAccount?"":'
    ",s='
    '+a+""+o+"
    ",r=t.created_at,h=t.replies_count,m=t.reblogs_count,d=t.favourites_count),l=this.#x(r);const c=`\n
    \n ${t.pinned?'':""}\n \n \n ${t.edited_at?" *":""}\n \n
    `;let p="";if("0"!==this.mtSettings.txtMaxLines){const e=0!==this.mtSettings.txtMaxLines.length?" truncate":"";p=""!==t.spoiler_text?'
    '+this.#y(t.spoiler_text)+'
    '+this.#y(t.content)+"
    ":t.reblog&&""!==t.reblog.content&&""!==t.reblog.spoiler_text?'
    '+this.#y(t.reblog.spoiler_text)+'
    '+this.#y(t.reblog.content)+"
    ":t.reblog&&""!==t.reblog.content&&""===t.reblog.spoiler_text?'
    '+this.#y(t.reblog.content)+"
    ":'
    '+this.#y(t.content)+"
    "}let g=[];if(t.media_attachments.length>0)for(let e in t.media_attachments)g.push(this.#L(t.media_attachments[e],t.sensitive));if(t.reblog&&t.reblog.media_attachments.length>0)for(let e in t.reblog.media_attachments)g.push(this.#L(t.reblog.media_attachments[e],t.reblog.sensitive));g=`
    ${g.join("")}
    `;let u="";!this.mtSettings.hidePreviewLink&&t.card&&(u=this.#T(t.card));let v="";if(t.poll){let e="";for(let i in t.poll.options)e+="
  • "+t.poll.options[i].title+"
  • ";v='
      '+e+"
    "}let b="";if(!this.mtSettings.hideCounterBar){b='
    '+('
    '+h+"
    ")+('
    '+m+"
    ")+('
    '+d+"
    ")+"
    "}return'
    '+i+s+c+"
    "+p+g+u+v+b+"
    "}#N(t,e){function i(t,e){let i=e.replace(/\s+/g,"").toLowerCase();return!(!["src","href","xlink:href"].includes(t)||!i.includes("javascript:")&&!i.includes("data:"))||(!!t.startsWith("on")||void 0)}function s(t){let e=t.attributes;for(let{name:s,value:a}of e)i(s,a)&&t.removeAttribute(s)}let a=(new DOMParser).parseFromString(t,"text/html").body||document.createElement("body");return function(t){let e=t.querySelectorAll("script");for(let t of e)t.remove()}(a),function t(e){let i=e.children;for(let e of i)s(e),t(e)}(a),e?a.childNodes:a.innerHTML}#y(t){let e=t;return e=this.#N(e,!1),e=this.#M(e),this.mtSettings.hideEmojos||(e=this.#S(e,this.fetchedData.emojos)),this.mtSettings.markdownBlockquote&&(e=this.#P(e,"

    >","

    ","

    ","

    ")),e}#M(t){let e=t.replaceAll('rel="tag"','rel="tag" target="_blank"');return e=e.replaceAll('class="u-url mention"','class="u-url mention" target="_blank"'),e}#P(t,e,i,s,a){if(t.includes(e)){const o=new RegExp(e+"(.*?)"+i,"gi");return t.replace(o,s+"$1"+a)}return t}#f(t){return(t??"").replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'")}#S(t,e){if(t.includes(":")){for(const i of e){const e=new RegExp(`\\:${i.shortcode}\\:`,"g");t=t.replace(e,`Emoji ${i.shortcode}`)}return t}return t}#x(t){const e=new Date(t);return new Intl.DateTimeFormat(this.mtSettings.dateLocale,this.mtSettings.dateOptions).format(e)}#L(t,e=!1){const i=t.type,s=e;let a="";const o='";return"image"===i&&(a='
    '+(s?o:"")+''+(t.description?this.#f(t.description):
    '),"audio"===i&&(a=t.preview_url?'
    '+(s?o:"")+''+(t.description?this.#f(t.description):
    ':'
    '+(s?o:"")+'
    '),"video"!==i&&"gifv"!==i||(a=this.mtSettings.hideVideoPreview?'
    '+(s?o:"")+'
    ':'
    '+(s?o:"")+''+(t.description?this.#f(t.description):
    '),a}#C(t,e){let i=document.createElement("dialog");i.id=t,i.classList.add("mt-dialog"),i.dataset.theme=this.mtContainerNode.getAttribute("data-theme"),i.innerHTML=e,document.body.prepend(i),i.showModal(),i.addEventListener("close",(()=>{document.body.removeChild(i)}))}#E(t){const e=Array.from(t.target.parentNode.parentNode.children).filter((t=>!t.classList.contains("mt-post-media-spoiler"))),i=e.indexOf(t.target.parentNode)+1;let s=[];e.forEach(((t,e)=>{let i="";i="gifv"===t.getAttribute("data-media-type")||"video"===t.getAttribute("data-media-type")?`\n \n `:`\n ${t.getAttribute(\n `;const a=`\n \n `;s.push(a)}));const a=`\n \n\n \n\n \n\n \n `;this.#C("mt-carousel",a),s.length>=2&&this.#k(e.length,i)}#k(t,e){let i=e;const s=document.getElementById("mt-carousel-scroll");let a=0,o=!1;const n=document.getElementById("mt-carousel-prev"),r=document.getElementById("mt-carousel-next"),l=(t,e="smooth")=>{document.getElementById("mt-carousel-"+t).scrollIntoView({behavior:e})};l(i,"instant");const d=()=>{clearTimeout(a),a=setTimeout((()=>{o&&(i=(()=>{const t=(s.scrollLeft+s.clientWidth)/s.clientWidth;return Math.round(t+Number.EPSILON)})(),m()),o=!0}),60)};s.addEventListener("scroll",d);const m=()=>{n.hidden=1===i,r.hidden=i===t},h=e=>{const s=e.target.closest("button")?.id;"mt-carousel-next"===s?(o=!1,++i,i>t&&(i=t),l(i),m()):"mt-carousel-prev"===s&&(o=!1,--i,i<1&&(i=1),l(i),m()),"mt-carousel-close"===s&&p()};document.addEventListener("click",h);const c=t=>{"Escape"!==t.key&&27!==t.keyCode||p()};document.addEventListener("keydown",c);const p=()=>{s.removeEventListener("scroll",d),document.removeEventListener("click",h),document.removeEventListener("keydown",c)}}#$(t){const e=t.target.closest("[data-media-type]"),i=e.dataset.mediaUrlHd;e.replaceChildren(),e.innerHTML=``}#_(t){const e=t.target,i=e.nextSibling;e.textContent==this.mtSettings.btnShowMore?(i.classList.remove("spoiler-txt-hidden"),i.classList.add("spoiler-txt-visible"),e.setAttribute("aria-expanded","true"),e.textContent=this.mtSettings.btnShowLess):(i.classList.remove("spoiler-txt-visible"),i.classList.add("spoiler-txt-hidden"),e.setAttribute("aria-expanded","false"),e.textContent=this.mtSettings.btnShowMore)}#H(t){const e=t.target;e.classList.contains("mt-btn-spoiler-media-show")?e.parentNode.classList.remove("mt-post-media-spoiler"):e.parentNode.classList.add("mt-post-media-spoiler")}#T(t){let e="";if("0"!==this.mtSettings.previewMaxLines&&t.description){e=''+this.#A(t.description)+""}return''+(t.image?'
    '+this.#f(t.image_description)+'
    ':'
    📄
    ')+'
    '+(t.provider_name?''+this.#A(t.provider_name)+"":"")+''+t.title+""+e+(t.author_name?''+this.#A(t.author_name)+"":"")+"
    "}#A(t){return(new DOMParser).parseFromString(t,"text/html").body.textContent}#b(){let t="",e="";if(this.mtSettings.btnSeeMore){let e="";"profile"===this.mtSettings.timelineType?this.mtSettings.profileName?e=this.mtSettings.profileName:this.#n("Please check your profileName value","⚠️"):"hashtag"===this.mtSettings.timelineType?e="tags/"+this.mtSettings.hashtagName:"local"===this.mtSettings.timelineType&&(e="public/local"),t=`\n \n ${this.mtSettings.btnSeeMore}\n `}if(this.mtSettings.btnReload&&(e=`\n `),this.mtBodyNode.parentNode.insertAdjacentHTML("beforeend",'"),this.mtSettings.btnReload){this.mtContainerNode.getElementsByClassName("btn-refresh")[0].addEventListener("click",(()=>{this.mtUpdate()}))}}#v(){this.mtBodyNode.addEventListener("click",(t=>{console.log("click on: ",t);const e=t.target,i=e.localName,s=e.parentNode;("article"==i||"article"==e.offsetParent?.localName||this.mtSettings.disableCarousel&&"image"===s.getAttribute("data-media-type"))&&this.#B(t),e.classList.contains("mt-btn-spoiler-txt")&&this.#_(t),e.classList.contains("mt-btn-spoiler-media")&&this.#H(t),this.mtSettings.disableCarousel||"img"!=i||"image"!==s.getAttribute("data-media-type")&&"audio"!==s.getAttribute("data-media-type")||this.#E(t),("mt-btn-play"==e.className||"svg"==i&&"mt-btn-play"==s.className||"path"==i&&"mt-btn-play"==s.parentNode.className||"img"==i&&("video"===s.getAttribute("data-media-type")||"gifv"===s.getAttribute("data-media-type")))&&(console.log("loadPostVideo"),this.#$(t))})),this.mtBodyNode.addEventListener("keydown",(t=>{const e=t.target.localName;"Enter"===t.key&&"article"==e&&this.#B(t)}))}#B(t){const e=t.target.closest(".mt-post").dataset.location;"a"!==t.target.localName&&"span"!==t.target.localName&&"button"!==t.target.localName&&"bdi"!==t.target.localName&&"time"!==t.target.localName&&!t.target.classList.contains("mt-post-media-spoiler")&&"mt-post-preview-noImage"!==t.target.className&&"mt-post-avatar-image-big"!==t.target.parentNode.className&&"mt-post-avatar-image-small"!==t.target.parentNode.className&&"mt-post-header-user-name"!==t.target.parentNode.className&&"mt-post-preview-image"!==t.target.parentNode.className&&"mt-post-preview"!==t.target.parentNode.className&&e&&window.open(e,"_blank","noopener")}#p(){const t=e=>{e.target.parentNode.classList.remove(this.mtSettings.spinnerClass),e.target.removeEventListener("load",t),e.target.removeEventListener("error",t)};this.mtBodyNode.querySelectorAll(`.${this.mtSettings.spinnerClass} > img`).forEach((e=>{e.addEventListener("load",t),e.addEventListener("error",t)}))}#n(t,e){const i=e||"❌";throw this.mtBodyNode.innerHTML=`\n
    \n ${i}\n Oops, something's happened:\n
    ${t}
    \n
    `,this.mtBodyNode.setAttribute("role","none"),new Error("Stopping the script due to an error building the timeline.")}}export{t as Init}; diff --git a/dist/mastodon-timeline.umd.js b/dist/mastodon-timeline.umd.js index fe355e7..6046f1b 100644 --- a/dist/mastodon-timeline.umd.js +++ b/dist/mastodon-timeline.umd.js @@ -2,7 +2,7 @@ /** * Mastodon embed timeline * @author idotj - * @version 4.3.12 + * @version 4.4.1 * @url https://gitlab.com/idotj/mastodon-embed-timeline * @license GNU AGPLv3 - */t.Init=class{constructor(t={}){this.defaultSettings={mtContainerId:"mt-container",instanceUrl:"https://mastodon.social",timelineType:"local",userId:"",profileName:"",hashtagName:"",spinnerClass:"mt-loading-spinner",defaultTheme:"auto",maxNbPostFetch:"20",maxNbPostShow:"20",dateLocale:"en-GB",dateOptions:{day:"2-digit",month:"short",year:"numeric"},hideUnlisted:!1,hideReblog:!1,hideReplies:!1,hidePinnedPosts:!1,hideUserAccount:!1,txtMaxLines:"",btnShowMore:"SHOW MORE",btnShowLess:"SHOW LESS",markdownBlockquote:!1,hideEmojos:!1,btnShowContent:"SHOW CONTENT",hideVideoPreview:!1,hidePreviewLink:!1,previewMaxLines:"",hideCounterBar:!1,disableCarousel:!1,carouselCloseTxt:"Close carousel",carouselPrevTxt:"Previous media item",carouselNextTxt:"Next media item",btnSeeMore:"See more posts at Mastodon",btnReload:"Refresh",insistSearchContainer:!1,insistSearchContainerTime:"3000"},this.mtSettings={...this.defaultSettings,...t},this.#t(),this.linkHeader={},this.mtContainerNode="",this.mtBodyNode="",this.fetchedData={},this.#e((()=>{this.#i()}))}#t(){Number(this.mtSettings.maxNbPostShow)>Number(this.mtSettings.maxNbPostFetch)&&(console.error(`Please check your settings! The maximum number of posts to show is bigger than the maximum number of posts to fetch. Changing the value of "maxNbPostFetch" to: ${this.mtSettings.maxNbPostShow}`),this.mtSettings.maxNbPostFetch=this.mtSettings.maxNbPostShow)}#e(t){"undefined"!=typeof document&&"complete"===document.readyState?t():"undefined"!=typeof document&&"complete"!==document.readyState&&document.addEventListener("DOMContentLoaded",t())}#i(){const t=()=>{this.mtContainerNode=document.getElementById(this.mtSettings.mtContainerId),this.mtBodyNode=this.mtContainerNode.getElementsByClassName("mt-body")[0],this.#s(),this.#a("newTimeline")};if(this.mtSettings.insistSearchContainer){const e=performance.now(),i=()=>{if(document.getElementById(this.mtSettings.mtContainerId))t();else{performance.now()-e container with id: "${this.mtSettings.mtContainerId}" after several attempts for ${this.mtSettings.insistSearchContainerTime/1e3} seconds`)}};i()}else document.getElementById(this.mtSettings.mtContainerId)?t():console.error(`Impossible to find the
    container with id: "${this.mtSettings.mtContainerId}". Please try to add the option 'insistSearchContainer: true' when initializing the script`)}mtUpdate(){this.#e((()=>{this.mtBodyNode.replaceChildren(),this.mtBodyNode.insertAdjacentHTML("afterbegin",'
    '),this.#a("updateTimeline")}))}mtColorTheme(t){this.#e((()=>{this.mtContainerNode.setAttribute("data-theme",t)}))}#s(){if("auto"===this.mtSettings.defaultTheme){let t=window.matchMedia("(prefers-color-scheme: dark)");t.matches?this.mtColorTheme("dark"):this.mtColorTheme("light"),t.addEventListener("change",(t=>{t.matches?this.mtColorTheme("dark"):this.mtColorTheme("light")}))}else this.mtColorTheme(this.mtSettings.defaultTheme)}#o(){return new Promise(((t,e)=>{const i=this.mtSettings.instanceUrl?`${this.mtSettings.instanceUrl}/api/v1/`:this.#n("Please check your instanceUrl value","⚠️"),s=this.#r(i),a=Object.entries(s).map((([t,i])=>{const s="timeline"===t;return this.#l(i,s).then((e=>({[t]:e}))).catch((s=>(e(new Error(`Something went wrong fetching data from: ${i}`)),this.#n(s.message),{[t]:[]})))}));Promise.all(a).then((async e=>{if(this.fetchedData=e.reduce(((t,e)=>({...t,...e})),{}),!this.mtSettings.hidePinnedPosts&&void 0!==this.fetchedData.pinned?.length&&0!==this.fetchedData.pinned.length){const t=this.fetchedData.pinned.map((t=>({...t,pinned:!0})));this.fetchedData.timeline=[...t,...this.fetchedData.timeline]}if(this.#d())t();else{do{await this.#m()}while(!this.#d()&&this.linkHeader.next);t()}}))}))}#r(t){let e={};return"profile"===this.mtSettings.timelineType?this.mtSettings.userId?(e.timeline=`${t}accounts/${this.mtSettings.userId}/statuses?limit=${this.mtSettings.maxNbPostFetch}`,this.mtSettings.hidePinnedPosts||(e.pinned=`${t}accounts/${this.mtSettings.userId}/statuses?pinned=true`)):this.#n("Please check your userId value","⚠️"):"hashtag"===this.mtSettings.timelineType?this.mtSettings.hashtagName?e.timeline=`${t}timelines/tag/${this.mtSettings.hashtagName}?limit=${this.mtSettings.maxNbPostFetch}`:this.#n("Please check your hashtagName value","⚠️"):"local"===this.mtSettings.timelineType?e.timeline=`${t}timelines/public?local=true&limit=${this.mtSettings.maxNbPostFetch}`:this.#n("Please check your timelineType value","⚠️"),this.mtSettings.hideEmojos||(e.emojos=`${t}custom_emojis`),e}async#l(t,e=!1){const i=await fetch(t);if(!i.ok)throw new Error(`\n Failed to fetch the following Url:
    ${t}
    Error status: ${i.status}
    Error message: ${i.statusText}\n `);const s=await i.json();return e&&i.headers.get("Link")&&(this.linkHeader=this.#h(i.headers.get("Link"))),s}#d(){return this.fetchedData.timeline.length>=Number(this.mtSettings.maxNbPostFetch)}#m(){return new Promise((t=>{this.linkHeader.next?this.#l(this.linkHeader.next,!0).then((e=>{this.fetchedData.timeline=[...this.fetchedData.timeline,...e],t()})):t()}))}#h(t){const e=t.split(", ").map((t=>t.split("; "))).map((t=>[t[1].replace(/"/g,"").replace("rel=",""),t[0].slice(1,-1)]));return Object.fromEntries(e)}async#a(t){await this.#o();const e=this.fetchedData.timeline;let i=0;if(this.mtBodyNode.replaceChildren(),e.forEach((t=>{const e="public"===t.visibility||!this.mtSettings.hideUnlisted&&"unlisted"===t.visibility,s=this.mtSettings.hideReblog&&t.reblog,a=this.mtSettings.hideReplies&&t.in_reply_to_id;!e||s||a||i${e?.length||0} posts have been fetched from the server
    This may be due to an incorrect configuration with the parameters or with the filters applied (to hide certains type of posts)`;this.#n(t,"📭")}}#g(){"0"!==this.mtSettings.txtMaxLines&&0!==this.mtSettings.txtMaxLines.length&&this.mtBodyNode.parentNode.style.setProperty("--mt-txt-max-lines",this.mtSettings.txtMaxLines),"0"!==this.mtSettings.previewMaxLines&&0!==this.mtSettings.previewMaxLines.length&&this.mtBodyNode.parentNode.style.setProperty("--mt-preview-max-lines",this.mtSettings.previewMaxLines)}#u(t){const e=this.mtBodyNode.getElementsByTagName("article");for(let i=0;i
    '+this.#f(t.reblog.account.username)+' avatar
    '+this.#f(t.account.username)+' avatar
    ',a=!this.mtSettings.hideEmojos&&t.reblog.account.display_name?this.#S(t.reblog.account.display_name,t.reblog.account.emojis):t.reblog.account.display_name?t.reblog.account.display_name:t.reblog.account.username,o=this.mtSettings.hideUserAccount?"":'
    ",s='
    '+a+""+o+"
    ",r=t.reblog.created_at,h=t.reblog.replies_count,m=t.reblog.reblogs_count,d=t.reblog.favourites_count):(n=t.url,i='
    '+this.#f(t.account.username)+' avatar
    ',a=!this.mtSettings.hideEmojos&&t.account.display_name?this.#S(t.account.display_name,t.account.emojis):t.account.display_name?t.account.display_name:t.account.username,o=this.mtSettings.hideUserAccount?"":'
    ",s='
    '+a+""+o+"
    ",r=t.created_at,h=t.replies_count,m=t.reblogs_count,d=t.favourites_count),l=this.#x(r);const c=`\n
    \n ${t.pinned?'':""}\n \n \n ${t.edited_at?" *":""}\n \n
    `;let p="";if("0"!==this.mtSettings.txtMaxLines){const e=0!==this.mtSettings.txtMaxLines.length?" truncate":"";p=""!==t.spoiler_text?'
    '+t.spoiler_text+'
    '+this.#y(t.content)+"
    ":t.reblog&&""!==t.reblog.content&&""!==t.reblog.spoiler_text?'
    '+t.reblog.spoiler_text+'
    '+this.#y(t.reblog.content)+"
    ":t.reblog&&""!==t.reblog.content&&""===t.reblog.spoiler_text?'
    '+this.#y(t.reblog.content)+"
    ":'
    '+this.#y(t.content)+"
    "}let g=[];if(t.media_attachments.length>0)for(let e in t.media_attachments)g.push(this.#L(t.media_attachments[e],t.sensitive));if(t.reblog&&t.reblog.media_attachments.length>0)for(let e in t.reblog.media_attachments)g.push(this.#L(t.reblog.media_attachments[e],t.reblog.sensitive));g=`
    ${g.join("")}
    `;let u="";!this.mtSettings.hidePreviewLink&&t.card&&(u=this.#N(t.card));let v="";if(t.poll){let e="";for(let i in t.poll.options)e+="
  • "+t.poll.options[i].title+"
  • ";v='
      '+e+"
    "}let b="";if(!this.mtSettings.hideCounterBar){b='
    '+('
    '+h+"
    ")+('
    '+m+"
    ")+('
    '+d+"
    ")+"
    "}return'
    '+i+s+c+"
    "+p+g+u+v+b+"
    "}#y(t){let e=t;return e=this.#T(e),this.mtSettings.hideEmojos||(e=this.#S(e,this.fetchedData.emojos)),this.mtSettings.markdownBlockquote&&(e=this.#M(e,"

    >","

    ","

    ","

    ")),e}#T(t){let e=t.replaceAll('rel="tag"','rel="tag" target="_blank"');return e=e.replaceAll('class="u-url mention"','class="u-url mention" target="_blank"'),e}#M(t,e,i,s,a){if(t.includes(e)){const o=new RegExp(e+"(.*?)"+i,"gi");return t.replace(o,s+"$1"+a)}return t}#f(t){return(t??"").replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'")}#S(t,e){if(t.includes(":")){for(const i of e){const e=new RegExp(`\\:${i.shortcode}\\:`,"g");t=t.replace(e,`Emoji ${i.shortcode}`)}return t}return t}#x(t){const e=new Date(t);return new Intl.DateTimeFormat(this.mtSettings.dateLocale,this.mtSettings.dateOptions).format(e)}#L(t,e=!1){const i=t.type,s=e;let a="";const o='";return"image"===i&&(a='
    '+(s?o:"")+''+(t.description?this.#f(t.description):
    '),"audio"===i&&(a=t.preview_url?'
    '+(s?o:"")+''+(t.description?this.#f(t.description):
    ':'
    '+(s?o:"")+'
    '),"video"!==i&&"gifv"!==i||(a=this.mtSettings.hideVideoPreview?'
    '+(s?o:"")+'
    ':'
    '+(s?o:"")+''+(t.description?this.#f(t.description):
    '),a}#C(t,e){let i=document.createElement("dialog");i.id=t,i.classList.add("mt-dialog"),i.dataset.theme=this.mtContainerNode.getAttribute("data-theme"),i.innerHTML=e,document.body.prepend(i),i.showModal(),i.addEventListener("close",(()=>{document.body.removeChild(i)}))}#P(t){const e=Array.from(t.target.parentNode.parentNode.children).filter((t=>!t.classList.contains("mt-post-media-spoiler"))),i=e.indexOf(t.target.parentNode)+1;let s=[];e.forEach(((t,e)=>{let i="";i="gifv"===t.getAttribute("data-media-type")||"video"===t.getAttribute("data-media-type")?`\n \n `:`\n ${t.getAttribute(\n `;const a=`\n \n `;s.push(a)}));const a=`\n \n\n \n\n \n\n \n `;this.#C("mt-carousel",a),s.length>=2&&this.#E(e.length,i)}#E(t,e){let i=e;const s=document.getElementById("mt-carousel-scroll");let a=0,o=!1;const n=document.getElementById("mt-carousel-prev"),r=document.getElementById("mt-carousel-next"),l=(t,e="smooth")=>{document.getElementById("mt-carousel-"+t).scrollIntoView({behavior:e})};l(i,"instant");const d=()=>{clearTimeout(a),a=setTimeout((()=>{o&&(i=(()=>{const t=(s.scrollLeft+s.clientWidth)/s.clientWidth;return Math.round(t+Number.EPSILON)})(),m()),o=!0}),60)};s.addEventListener("scroll",d);const m=()=>{n.hidden=1===i,r.hidden=i===t},h=e=>{const s=e.target.closest("button")?.id;"mt-carousel-next"===s?(o=!1,++i,i>t&&(i=t),l(i),m()):"mt-carousel-prev"===s&&(o=!1,--i,i<1&&(i=1),l(i),m()),"mt-carousel-close"===s&&p()};document.addEventListener("click",h);const c=t=>{"Escape"!==t.key&&27!==t.keyCode||p()};document.addEventListener("keydown",c);const p=()=>{s.removeEventListener("scroll",d),document.removeEventListener("click",h),document.removeEventListener("keydown",c)}}#k(t){const e=t.target.closest("[data-media-type]"),i=e.dataset.mediaUrlHd;e.replaceChildren(),e.innerHTML=``}#$(t){const e=t.target,i=e.nextSibling;e.textContent==this.mtSettings.btnShowMore?(i.classList.remove("spoiler-txt-hidden"),i.classList.add("spoiler-txt-visible"),e.setAttribute("aria-expanded","true"),e.textContent=this.mtSettings.btnShowLess):(i.classList.remove("spoiler-txt-visible"),i.classList.add("spoiler-txt-hidden"),e.setAttribute("aria-expanded","false"),e.textContent=this.mtSettings.btnShowMore)}#_(t){const e=t.target;e.classList.contains("mt-btn-spoiler-media-show")?e.parentNode.classList.remove("mt-post-media-spoiler"):e.parentNode.classList.add("mt-post-media-spoiler")}#N(t){let e="";if("0"!==this.mtSettings.previewMaxLines&&t.description){e=''+this.#A(t.description)+""}return''+(t.image?'
    '+this.#f(t.image_description)+'
    ':'
    📄
    ')+'
    '+(t.provider_name?''+this.#A(t.provider_name)+"":"")+''+t.title+""+e+(t.author_name?''+this.#A(t.author_name)+"":"")+"
    "}#A(t){return(new DOMParser).parseFromString(t,"text/html").body.textContent}#b(){let t="",e="";if(this.mtSettings.btnSeeMore){let e="";"profile"===this.mtSettings.timelineType?this.mtSettings.profileName?e=this.mtSettings.profileName:this.#n("Please check your profileName value","⚠️"):"hashtag"===this.mtSettings.timelineType?e="tags/"+this.mtSettings.hashtagName:"local"===this.mtSettings.timelineType&&(e="public/local"),t=`\n \n ${this.mtSettings.btnSeeMore}\n `}if(this.mtSettings.btnReload&&(e=`\n `),this.mtBodyNode.parentNode.insertAdjacentHTML("beforeend",'"),this.mtSettings.btnReload){this.mtContainerNode.getElementsByClassName("btn-refresh")[0].addEventListener("click",(()=>{this.mtUpdate()}))}}#v(){this.mtBodyNode.addEventListener("click",(t=>{const e=t.target,i=e.localName,s=e.parentNode;("article"==i||"article"==e.offsetParent?.localName||this.mtSettings.disableCarousel&&"image"===s.getAttribute("data-media-type"))&&this.#H(t),e.classList.contains("mt-btn-spoiler-txt")&&this.#$(t),e.classList.contains("mt-btn-spoiler-media")&&this.#_(t),this.mtSettings.disableCarousel||"img"!=i||"image"!==s.getAttribute("data-media-type")&&"audio"!==s.getAttribute("data-media-type")||this.#P(t),("mt-btn-play"==e.className||"svg"==i&&"mt-btn-play"==s.className||"path"==i&&"mt-btn-play"==s.parentNode.className||"img"==i&&("video"===s.getAttribute("data-media-type")||"gifv"===s.getAttribute("data-media-type")))&&this.#k(t)})),this.mtBodyNode.addEventListener("keydown",(t=>{const e=t.target.localName;"Enter"===t.key&&"article"==e&&this.#H(t)}))}#H(t){const e=t.target.closest(".mt-post").dataset.location;"a"!==t.target.localName&&"span"!==t.target.localName&&"button"!==t.target.localName&&"bdi"!==t.target.localName&&"time"!==t.target.localName&&!t.target.classList.contains("mt-post-media-spoiler")&&"mt-post-preview-noImage"!==t.target.className&&"mt-post-avatar-image-big"!==t.target.parentNode.className&&"mt-post-avatar-image-small"!==t.target.parentNode.className&&"mt-post-header-user-name"!==t.target.parentNode.className&&"mt-post-preview-image"!==t.target.parentNode.className&&"mt-post-preview"!==t.target.parentNode.className&&e&&window.open(e,"_blank","noopener")}#p(){const t=e=>{e.target.parentNode.classList.remove(this.mtSettings.spinnerClass),e.target.removeEventListener("load",t),e.target.removeEventListener("error",t)};this.mtBodyNode.querySelectorAll(`.${this.mtSettings.spinnerClass} > img`).forEach((e=>{e.addEventListener("load",t),e.addEventListener("error",t)}))}#n(t,e){const i=e||"❌";throw this.mtBodyNode.innerHTML=`\n
    \n ${i}\n Oops, something's happened:\n
    ${t}
    \n
    `,this.mtBodyNode.setAttribute("role","none"),new Error("Stopping the script due to an error building the timeline.")}}})); + */t.Init=class{constructor(t={}){this.defaultSettings={mtContainerId:"mt-container",instanceUrl:"https://mastodon.social",timelineType:"local",userId:"",profileName:"",hashtagName:"",spinnerClass:"mt-loading-spinner",defaultTheme:"auto",maxNbPostFetch:"20",maxNbPostShow:"20",dateLocale:"en-GB",dateOptions:{day:"2-digit",month:"short",year:"numeric"},hideUnlisted:!1,hideReblog:!1,hideReplies:!1,hidePinnedPosts:!1,hideUserAccount:!1,txtMaxLines:"",btnShowMore:"SHOW MORE",btnShowLess:"SHOW LESS",markdownBlockquote:!1,hideEmojos:!1,btnShowContent:"SHOW CONTENT",hideVideoPreview:!1,btnPlayVideoTxt:"Load and play video",hidePreviewLink:!1,previewMaxLines:"",hideCounterBar:!1,disableCarousel:!1,carouselCloseTxt:"Close carousel",carouselPrevTxt:"Previous media item",carouselNextTxt:"Next media item",btnSeeMore:"See more posts at Mastodon",btnReload:"Refresh",insistSearchContainer:!1,insistSearchContainerTime:"3000"},this.mtSettings={...this.defaultSettings,...t},this.#t(),this.linkHeader={},this.mtContainerNode="",this.mtBodyNode="",this.fetchedData={},this.#e((()=>{this.#i()}))}#t(){Number(this.mtSettings.maxNbPostShow)>Number(this.mtSettings.maxNbPostFetch)&&(console.error(`Please check your settings! The maximum number of posts to show is bigger than the maximum number of posts to fetch. Changing the value of "maxNbPostFetch" to: ${this.mtSettings.maxNbPostShow}`),this.mtSettings.maxNbPostFetch=this.mtSettings.maxNbPostShow)}#e(t){"undefined"!=typeof document&&"complete"===document.readyState?t():"undefined"!=typeof document&&"complete"!==document.readyState&&document.addEventListener("DOMContentLoaded",t())}#i(){const t=()=>{this.mtContainerNode=document.getElementById(this.mtSettings.mtContainerId),this.mtBodyNode=this.mtContainerNode.getElementsByClassName("mt-body")[0],this.#s(),this.#a("newTimeline")};if(this.mtSettings.insistSearchContainer){const e=performance.now(),i=()=>{if(document.getElementById(this.mtSettings.mtContainerId))t();else{performance.now()-e container with id: "${this.mtSettings.mtContainerId}" after several attempts for ${this.mtSettings.insistSearchContainerTime/1e3} seconds`)}};i()}else document.getElementById(this.mtSettings.mtContainerId)?t():console.error(`Impossible to find the
    container with id: "${this.mtSettings.mtContainerId}". Please try to add the option 'insistSearchContainer: true' when initializing the script`)}mtUpdate(){this.#e((()=>{this.mtBodyNode.replaceChildren(),this.mtBodyNode.insertAdjacentHTML("afterbegin",'
    '),this.#a("updateTimeline")}))}mtColorTheme(t){this.#e((()=>{this.mtContainerNode.setAttribute("data-theme",t)}))}#s(){if("auto"===this.mtSettings.defaultTheme){let t=window.matchMedia("(prefers-color-scheme: dark)");t.matches?this.mtColorTheme("dark"):this.mtColorTheme("light"),t.addEventListener("change",(t=>{t.matches?this.mtColorTheme("dark"):this.mtColorTheme("light")}))}else this.mtColorTheme(this.mtSettings.defaultTheme)}#o(){return new Promise(((t,e)=>{const i=this.mtSettings.instanceUrl?`${this.mtSettings.instanceUrl}/api/v1/`:this.#n("Please check your instanceUrl value","⚠️"),s=this.#r(i),a=Object.entries(s).map((([t,i])=>{const s="timeline"===t;return this.#l(i,s).then((e=>({[t]:e}))).catch((s=>(e(new Error(`Something went wrong fetching data from: ${i}`)),this.#n(s.message),{[t]:[]})))}));Promise.all(a).then((async e=>{if(this.fetchedData=e.reduce(((t,e)=>({...t,...e})),{}),!this.mtSettings.hidePinnedPosts&&void 0!==this.fetchedData.pinned?.length&&0!==this.fetchedData.pinned.length){const t=this.fetchedData.pinned.map((t=>({...t,pinned:!0})));this.fetchedData.timeline=[...t,...this.fetchedData.timeline]}if(this.#d())t();else{do{await this.#m()}while(!this.#d()&&this.linkHeader.next);t()}}))}))}#r(t){let e={};return"profile"===this.mtSettings.timelineType?this.mtSettings.userId?(e.timeline=`${t}accounts/${this.mtSettings.userId}/statuses?limit=${this.mtSettings.maxNbPostFetch}`,this.mtSettings.hidePinnedPosts||(e.pinned=`${t}accounts/${this.mtSettings.userId}/statuses?pinned=true`)):this.#n("Please check your userId value","⚠️"):"hashtag"===this.mtSettings.timelineType?this.mtSettings.hashtagName?e.timeline=`${t}timelines/tag/${this.mtSettings.hashtagName}?limit=${this.mtSettings.maxNbPostFetch}`:this.#n("Please check your hashtagName value","⚠️"):"local"===this.mtSettings.timelineType?e.timeline=`${t}timelines/public?local=true&limit=${this.mtSettings.maxNbPostFetch}`:this.#n("Please check your timelineType value","⚠️"),this.mtSettings.hideEmojos||(e.emojos=`${t}custom_emojis`),e}async#l(t,e=!1){const i=await fetch(t);if(!i.ok)throw new Error(`\n Failed to fetch the following Url:
    ${t}
    Error status: ${i.status}
    Error message: ${i.statusText}\n `);const s=await i.json();return e&&i.headers.get("Link")&&(this.linkHeader=this.#h(i.headers.get("Link"))),s}#d(){return this.fetchedData.timeline.length>=Number(this.mtSettings.maxNbPostFetch)}#m(){return new Promise((t=>{this.linkHeader.next?this.#l(this.linkHeader.next,!0).then((e=>{this.fetchedData.timeline=[...this.fetchedData.timeline,...e],t()})):t()}))}#h(t){const e=t.split(", ").map((t=>t.split("; "))).map((t=>[t[1].replace(/"/g,"").replace("rel=",""),t[0].slice(1,-1)]));return Object.fromEntries(e)}async#a(t){await this.#o();const e=this.fetchedData.timeline;let i=0;if(this.mtBodyNode.replaceChildren(),e.forEach((t=>{const e="public"===t.visibility||!this.mtSettings.hideUnlisted&&"unlisted"===t.visibility,s=this.mtSettings.hideReblog&&t.reblog,a=this.mtSettings.hideReplies&&t.in_reply_to_id;!e||s||a||i${e?.length||0} posts have been fetched from the server
    This may be due to an incorrect configuration with the parameters or with the filters applied (to hide certains type of posts)`;this.#n(t,"📭")}}#g(){"0"!==this.mtSettings.txtMaxLines&&0!==this.mtSettings.txtMaxLines.length&&this.mtBodyNode.parentNode.style.setProperty("--mt-txt-max-lines",this.mtSettings.txtMaxLines),"0"!==this.mtSettings.previewMaxLines&&0!==this.mtSettings.previewMaxLines.length&&this.mtBodyNode.parentNode.style.setProperty("--mt-preview-max-lines",this.mtSettings.previewMaxLines)}#u(t){const e=this.mtBodyNode.getElementsByTagName("article");for(let i=0;i
    '+this.#f(t.reblog.account.username)+' avatar
    '+this.#f(t.account.username)+' avatar
    ',a=!this.mtSettings.hideEmojos&&t.reblog.account.display_name?this.#x(t.reblog.account.display_name,t.reblog.account.emojis):t.reblog.account.display_name?t.reblog.account.display_name:t.reblog.account.username,o=this.mtSettings.hideUserAccount?"":'
    ",s='
    '+a+""+o+"
    ",r=t.reblog.created_at,h=t.reblog.replies_count,m=t.reblog.reblogs_count,d=t.reblog.favourites_count):(n=t.url,i='
    '+this.#f(t.account.username)+' avatar
    ',a=!this.mtSettings.hideEmojos&&t.account.display_name?this.#x(t.account.display_name,t.account.emojis):t.account.display_name?t.account.display_name:t.account.username,o=this.mtSettings.hideUserAccount?"":'
    ",s='
    '+a+""+o+"
    ",r=t.created_at,h=t.replies_count,m=t.reblogs_count,d=t.favourites_count),l=this.#S(r);const c=`\n
    \n ${t.pinned?'':""}\n \n \n ${t.edited_at?" *":""}\n \n
    `;let p="";if("0"!==this.mtSettings.txtMaxLines){const e=0!==this.mtSettings.txtMaxLines.length?" truncate":"";p=""!==t.spoiler_text?'
    '+this.#y(t.spoiler_text)+'
    '+this.#y(t.content)+"
    ":t.reblog&&""!==t.reblog.content&&""!==t.reblog.spoiler_text?'
    '+this.#y(t.reblog.spoiler_text)+'
    '+this.#y(t.reblog.content)+"
    ":t.reblog&&""!==t.reblog.content&&""===t.reblog.spoiler_text?'
    '+this.#y(t.reblog.content)+"
    ":'
    '+this.#y(t.content)+"
    "}let g=[];if(t.media_attachments.length>0)for(let e in t.media_attachments)g.push(this.#L(t.media_attachments[e],t.sensitive));if(t.reblog&&t.reblog.media_attachments.length>0)for(let e in t.reblog.media_attachments)g.push(this.#L(t.reblog.media_attachments[e],t.reblog.sensitive));g=`
    ${g.join("")}
    `;let u="";!this.mtSettings.hidePreviewLink&&t.card&&(u=this.#T(t.card));let v="";if(t.poll){let e="";for(let i in t.poll.options)e+="
  • "+t.poll.options[i].title+"
  • ";v='
      '+e+"
    "}let b="";if(!this.mtSettings.hideCounterBar){b='
    '+('
    '+h+"
    ")+('
    '+m+"
    ")+('
    '+d+"
    ")+"
    "}return'
    '+i+s+c+"
    "+p+g+u+v+b+"
    "}#N(t,e){function i(t,e){let i=e.replace(/\s+/g,"").toLowerCase();return!(!["src","href","xlink:href"].includes(t)||!i.includes("javascript:")&&!i.includes("data:"))||(!!t.startsWith("on")||void 0)}function s(t){let e=t.attributes;for(let{name:s,value:a}of e)i(s,a)&&t.removeAttribute(s)}let a=(new DOMParser).parseFromString(t,"text/html").body||document.createElement("body");return function(t){let e=t.querySelectorAll("script");for(let t of e)t.remove()}(a),function t(e){let i=e.children;for(let e of i)s(e),t(e)}(a),e?a.childNodes:a.innerHTML}#y(t){let e=t;return e=this.#N(e,!1),e=this.#M(e),this.mtSettings.hideEmojos||(e=this.#x(e,this.fetchedData.emojos)),this.mtSettings.markdownBlockquote&&(e=this.#P(e,"

    >","

    ","

    ","

    ")),e}#M(t){let e=t.replaceAll('rel="tag"','rel="tag" target="_blank"');return e=e.replaceAll('class="u-url mention"','class="u-url mention" target="_blank"'),e}#P(t,e,i,s,a){if(t.includes(e)){const o=new RegExp(e+"(.*?)"+i,"gi");return t.replace(o,s+"$1"+a)}return t}#f(t){return(t??"").replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'")}#x(t,e){if(t.includes(":")){for(const i of e){const e=new RegExp(`\\:${i.shortcode}\\:`,"g");t=t.replace(e,`Emoji ${i.shortcode}`)}return t}return t}#S(t){const e=new Date(t);return new Intl.DateTimeFormat(this.mtSettings.dateLocale,this.mtSettings.dateOptions).format(e)}#L(t,e=!1){const i=t.type,s=e;let a="";const o='";return"image"===i&&(a='
    '+(s?o:"")+''+(t.description?this.#f(t.description):
    '),"audio"===i&&(a=t.preview_url?'
    '+(s?o:"")+''+(t.description?this.#f(t.description):
    ':'
    '+(s?o:"")+'
    '),"video"!==i&&"gifv"!==i||(a=this.mtSettings.hideVideoPreview?'
    '+(s?o:"")+'
    ':'
    '+(s?o:"")+''+(t.description?this.#f(t.description):
    '),a}#C(t,e){let i=document.createElement("dialog");i.id=t,i.classList.add("mt-dialog"),i.dataset.theme=this.mtContainerNode.getAttribute("data-theme"),i.innerHTML=e,document.body.prepend(i),i.showModal(),i.addEventListener("close",(()=>{document.body.removeChild(i)}))}#E(t){const e=Array.from(t.target.parentNode.parentNode.children).filter((t=>!t.classList.contains("mt-post-media-spoiler"))),i=e.indexOf(t.target.parentNode)+1;let s=[];e.forEach(((t,e)=>{let i="";i="gifv"===t.getAttribute("data-media-type")||"video"===t.getAttribute("data-media-type")?`\n \n `:`\n ${t.getAttribute(\n `;const a=`\n \n `;s.push(a)}));const a=`\n \n\n \n\n \n\n \n `;this.#C("mt-carousel",a),s.length>=2&&this.#k(e.length,i)}#k(t,e){let i=e;const s=document.getElementById("mt-carousel-scroll");let a=0,o=!1;const n=document.getElementById("mt-carousel-prev"),r=document.getElementById("mt-carousel-next"),l=(t,e="smooth")=>{document.getElementById("mt-carousel-"+t).scrollIntoView({behavior:e})};l(i,"instant");const d=()=>{clearTimeout(a),a=setTimeout((()=>{o&&(i=(()=>{const t=(s.scrollLeft+s.clientWidth)/s.clientWidth;return Math.round(t+Number.EPSILON)})(),m()),o=!0}),60)};s.addEventListener("scroll",d);const m=()=>{n.hidden=1===i,r.hidden=i===t},h=e=>{const s=e.target.closest("button")?.id;"mt-carousel-next"===s?(o=!1,++i,i>t&&(i=t),l(i),m()):"mt-carousel-prev"===s&&(o=!1,--i,i<1&&(i=1),l(i),m()),"mt-carousel-close"===s&&p()};document.addEventListener("click",h);const c=t=>{"Escape"!==t.key&&27!==t.keyCode||p()};document.addEventListener("keydown",c);const p=()=>{s.removeEventListener("scroll",d),document.removeEventListener("click",h),document.removeEventListener("keydown",c)}}#$(t){const e=t.target.closest("[data-media-type]"),i=e.dataset.mediaUrlHd;e.replaceChildren(),e.innerHTML=``}#_(t){const e=t.target,i=e.nextSibling;e.textContent==this.mtSettings.btnShowMore?(i.classList.remove("spoiler-txt-hidden"),i.classList.add("spoiler-txt-visible"),e.setAttribute("aria-expanded","true"),e.textContent=this.mtSettings.btnShowLess):(i.classList.remove("spoiler-txt-visible"),i.classList.add("spoiler-txt-hidden"),e.setAttribute("aria-expanded","false"),e.textContent=this.mtSettings.btnShowMore)}#H(t){const e=t.target;e.classList.contains("mt-btn-spoiler-media-show")?e.parentNode.classList.remove("mt-post-media-spoiler"):e.parentNode.classList.add("mt-post-media-spoiler")}#T(t){let e="";if("0"!==this.mtSettings.previewMaxLines&&t.description){e=''+this.#A(t.description)+""}return''+(t.image?'
    '+this.#f(t.image_description)+'
    ':'
    📄
    ')+'
    '+(t.provider_name?''+this.#A(t.provider_name)+"":"")+''+t.title+""+e+(t.author_name?''+this.#A(t.author_name)+"":"")+"
    "}#A(t){return(new DOMParser).parseFromString(t,"text/html").body.textContent}#b(){let t="",e="";if(this.mtSettings.btnSeeMore){let e="";"profile"===this.mtSettings.timelineType?this.mtSettings.profileName?e=this.mtSettings.profileName:this.#n("Please check your profileName value","⚠️"):"hashtag"===this.mtSettings.timelineType?e="tags/"+this.mtSettings.hashtagName:"local"===this.mtSettings.timelineType&&(e="public/local"),t=`\n \n ${this.mtSettings.btnSeeMore}\n `}if(this.mtSettings.btnReload&&(e=`\n `),this.mtBodyNode.parentNode.insertAdjacentHTML("beforeend",'"),this.mtSettings.btnReload){this.mtContainerNode.getElementsByClassName("btn-refresh")[0].addEventListener("click",(()=>{this.mtUpdate()}))}}#v(){this.mtBodyNode.addEventListener("click",(t=>{console.log("click on: ",t);const e=t.target,i=e.localName,s=e.parentNode;("article"==i||"article"==e.offsetParent?.localName||this.mtSettings.disableCarousel&&"image"===s.getAttribute("data-media-type"))&&this.#B(t),e.classList.contains("mt-btn-spoiler-txt")&&this.#_(t),e.classList.contains("mt-btn-spoiler-media")&&this.#H(t),this.mtSettings.disableCarousel||"img"!=i||"image"!==s.getAttribute("data-media-type")&&"audio"!==s.getAttribute("data-media-type")||this.#E(t),("mt-btn-play"==e.className||"svg"==i&&"mt-btn-play"==s.className||"path"==i&&"mt-btn-play"==s.parentNode.className||"img"==i&&("video"===s.getAttribute("data-media-type")||"gifv"===s.getAttribute("data-media-type")))&&(console.log("loadPostVideo"),this.#$(t))})),this.mtBodyNode.addEventListener("keydown",(t=>{const e=t.target.localName;"Enter"===t.key&&"article"==e&&this.#B(t)}))}#B(t){const e=t.target.closest(".mt-post").dataset.location;"a"!==t.target.localName&&"span"!==t.target.localName&&"button"!==t.target.localName&&"bdi"!==t.target.localName&&"time"!==t.target.localName&&!t.target.classList.contains("mt-post-media-spoiler")&&"mt-post-preview-noImage"!==t.target.className&&"mt-post-avatar-image-big"!==t.target.parentNode.className&&"mt-post-avatar-image-small"!==t.target.parentNode.className&&"mt-post-header-user-name"!==t.target.parentNode.className&&"mt-post-preview-image"!==t.target.parentNode.className&&"mt-post-preview"!==t.target.parentNode.className&&e&&window.open(e,"_blank","noopener")}#p(){const t=e=>{e.target.parentNode.classList.remove(this.mtSettings.spinnerClass),e.target.removeEventListener("load",t),e.target.removeEventListener("error",t)};this.mtBodyNode.querySelectorAll(`.${this.mtSettings.spinnerClass} > img`).forEach((e=>{e.addEventListener("load",t),e.addEventListener("error",t)}))}#n(t,e){const i=e||"❌";throw this.mtBodyNode.innerHTML=`\n
    \n ${i}\n Oops, something's happened:\n
    ${t}
    \n
    `,this.mtBodyNode.setAttribute("role","none"),new Error("Stopping the script due to an error building the timeline.")}}})); diff --git a/package-lock.json b/package-lock.json index 26e9393..80335b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,17 @@ { "name": "@idotj/mastodon-embed-timeline", - "version": "4.3.12", + "version": "4.4.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@idotj/mastodon-embed-timeline", - "version": "4.3.12", + "version": "4.4.1", "license": "GNU", "devDependencies": { "@rollup/plugin-terser": "^0.4.4", "clean-css-cli": "^5.6.3", - "rollup": "^4.12.0" + "rollup": "^4.14.0" } }, "node_modules/@jridgewell/gen-mapping": { @@ -95,9 +95,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.12.0.tgz", - "integrity": "sha512-+ac02NL/2TCKRrJu2wffk1kZ+RyqxVUlbjSagNgPm94frxtr+XDL12E5Ll1enWskLrtrZ2r8L3wED1orIibV/w==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.14.0.tgz", + "integrity": "sha512-jwXtxYbRt1V+CdQSy6Z+uZti7JF5irRKF8hlKfEnF/xJpcNGuuiZMBvuoYM+x9sr9iWGnzrlM0+9hvQ1kgkf1w==", "cpu": [ "arm" ], @@ -108,9 +108,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.12.0.tgz", - "integrity": "sha512-OBqcX2BMe6nvjQ0Nyp7cC90cnumt8PXmO7Dp3gfAju/6YwG0Tj74z1vKrfRz7qAv23nBcYM8BCbhrsWqO7PzQQ==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.14.0.tgz", + "integrity": "sha512-fI9nduZhCccjzlsA/OuAwtFGWocxA4gqXGTLvOyiF8d+8o0fZUeSztixkYjcGq1fGZY3Tkq4yRvHPFxU+jdZ9Q==", "cpu": [ "arm64" ], @@ -121,9 +121,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.12.0.tgz", - "integrity": "sha512-X64tZd8dRE/QTrBIEs63kaOBG0b5GVEd3ccoLtyf6IdXtHdh8h+I56C2yC3PtC9Ucnv0CpNFJLqKFVgCYe0lOQ==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.14.0.tgz", + "integrity": "sha512-BcnSPRM76/cD2gQC+rQNGBN6GStBs2pl/FpweW8JYuz5J/IEa0Fr4AtrPv766DB/6b2MZ/AfSIOSGw3nEIP8SA==", "cpu": [ "arm64" ], @@ -134,9 +134,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.12.0.tgz", - "integrity": "sha512-cc71KUZoVbUJmGP2cOuiZ9HSOP14AzBAThn3OU+9LcA1+IUqswJyR1cAJj3Mg55HbjZP6OLAIscbQsQLrpgTOg==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.14.0.tgz", + "integrity": "sha512-LDyFB9GRolGN7XI6955aFeI3wCdCUszFWumWU0deHA8VpR3nWRrjG6GtGjBrQxQKFevnUTHKCfPR4IvrW3kCgQ==", "cpu": [ "x64" ], @@ -147,9 +147,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.12.0.tgz", - "integrity": "sha512-a6w/Y3hyyO6GlpKL2xJ4IOh/7d+APaqLYdMf86xnczU3nurFTaVN9s9jOXQg97BE4nYm/7Ga51rjec5nfRdrvA==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.14.0.tgz", + "integrity": "sha512-ygrGVhQP47mRh0AAD0zl6QqCbNsf0eTo+vgwkY6LunBcg0f2Jv365GXlDUECIyoXp1kKwL5WW6rsO429DBY/bA==", "cpu": [ "arm" ], @@ -160,9 +160,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.12.0.tgz", - "integrity": "sha512-0fZBq27b+D7Ar5CQMofVN8sggOVhEtzFUwOwPppQt0k+VR+7UHMZZY4y+64WJ06XOhBTKXtQB/Sv0NwQMXyNAA==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.14.0.tgz", + "integrity": "sha512-x+uJ6MAYRlHGe9wi4HQjxpaKHPM3d3JjqqCkeC5gpnnI6OWovLdXTpfa8trjxPLnWKyBsSi5kne+146GAxFt4A==", "cpu": [ "arm64" ], @@ -173,9 +173,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.12.0.tgz", - "integrity": "sha512-eTvzUS3hhhlgeAv6bfigekzWZjaEX9xP9HhxB0Dvrdbkk5w/b+1Sxct2ZuDxNJKzsRStSq1EaEkVSEe7A7ipgQ==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.14.0.tgz", + "integrity": "sha512-nrRw8ZTQKg6+Lttwqo6a2VxR9tOroa2m91XbdQ2sUUzHoedXlsyvY1fN4xWdqz8PKmf4orDwejxXHjh7YBGUCA==", "cpu": [ "arm64" ], @@ -185,10 +185,23 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.14.0.tgz", + "integrity": "sha512-xV0d5jDb4aFu84XKr+lcUJ9y3qpIWhttO3Qev97z8DKLXR62LC3cXT/bMZXrjLF9X+P5oSmJTzAhqwUbY96PnA==", + "cpu": [ + "ppc64le" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.12.0.tgz", - "integrity": "sha512-ix+qAB9qmrCRiaO71VFfY8rkiAZJL8zQRXveS27HS+pKdjwUfEhqo2+YF2oI+H/22Xsiski+qqwIBxVewLK7sw==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.14.0.tgz", + "integrity": "sha512-SDDhBQwZX6LPRoPYjAZWyL27LbcBo7WdBFWJi5PI9RPCzU8ijzkQn7tt8NXiXRiFMJCVpkuMkBf4OxSxVMizAw==", "cpu": [ "riscv64" ], @@ -198,10 +211,23 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.14.0.tgz", + "integrity": "sha512-RxB/qez8zIDshNJDufYlTT0ZTVut5eCpAZ3bdXDU9yTxBzui3KhbGjROK2OYTTor7alM7XBhssgoO3CZ0XD3qA==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.12.0.tgz", - "integrity": "sha512-TenQhZVOtw/3qKOPa7d+QgkeM6xY0LtwzR8OplmyL5LrgTWIXpTQg2Q2ycBf8jm+SFW2Wt/DTn1gf7nFp3ssVA==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.14.0.tgz", + "integrity": "sha512-C6y6z2eCNCfhZxT9u+jAM2Fup89ZjiG5pIzZIDycs1IwESviLxwkQcFRGLjnDrP+PT+v5i4YFvlcfAs+LnreXg==", "cpu": [ "x64" ], @@ -212,9 +238,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.12.0.tgz", - "integrity": "sha512-LfFdRhNnW0zdMvdCb5FNuWlls2WbbSridJvxOvYWgSBOYZtgBfW9UGNJG//rwMqTX1xQE9BAodvMH9tAusKDUw==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.14.0.tgz", + "integrity": "sha512-i0QwbHYfnOMYsBEyjxcwGu5SMIi9sImDVjDg087hpzXqhBSosxkE7gyIYFHgfFl4mr7RrXksIBZ4DoLoP4FhJg==", "cpu": [ "x64" ], @@ -225,9 +251,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.12.0.tgz", - "integrity": "sha512-JPDxovheWNp6d7AHCgsUlkuCKvtu3RB55iNEkaQcf0ttsDU/JZF+iQnYcQJSk/7PtT4mjjVG8N1kpwnI9SLYaw==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.14.0.tgz", + "integrity": "sha512-Fq52EYb0riNHLBTAcL0cun+rRwyZ10S9vKzhGKKgeD+XbwunszSY0rVMco5KbOsTlwovP2rTOkiII/fQ4ih/zQ==", "cpu": [ "arm64" ], @@ -238,9 +264,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.12.0.tgz", - "integrity": "sha512-fjtuvMWRGJn1oZacG8IPnzIV6GF2/XG+h71FKn76OYFqySXInJtseAqdprVTDTyqPxQOG9Exak5/E9Z3+EJ8ZA==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.14.0.tgz", + "integrity": "sha512-e/PBHxPdJ00O9p5Ui43+vixSgVf4NlLsmV6QneGERJ3lnjIua/kim6PRFe3iDueT1rQcgSkYP8ZBBXa/h4iPvw==", "cpu": [ "ia32" ], @@ -251,9 +277,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.12.0.tgz", - "integrity": "sha512-ZYmr5mS2wd4Dew/JjT0Fqi2NPB/ZhZ2VvPp7SmvPZb4Y1CG/LRcS6tcRo2cYU7zLK5A7cdbhWnnWmUjoI4qapg==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.14.0.tgz", + "integrity": "sha512-aGg7iToJjdklmxlUlJh/PaPNa4PmqHfyRMLunbL3eaMO0gp656+q1zOKkpJ/CVe9CryJv6tAN1HDoR8cNGzkag==", "cpu": [ "x64" ], @@ -601,9 +627,9 @@ } }, "node_modules/rollup": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz", - "integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.14.0.tgz", + "integrity": "sha512-Qe7w62TyawbDzB4yt32R0+AbIo6m1/sqO7UPzFS8Z/ksL5mrfhA0v4CavfdmFav3D+ub4QeAgsGEe84DoWe/nQ==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -616,19 +642,21 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.12.0", - "@rollup/rollup-android-arm64": "4.12.0", - "@rollup/rollup-darwin-arm64": "4.12.0", - "@rollup/rollup-darwin-x64": "4.12.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.12.0", - "@rollup/rollup-linux-arm64-gnu": "4.12.0", - "@rollup/rollup-linux-arm64-musl": "4.12.0", - "@rollup/rollup-linux-riscv64-gnu": "4.12.0", - "@rollup/rollup-linux-x64-gnu": "4.12.0", - "@rollup/rollup-linux-x64-musl": "4.12.0", - "@rollup/rollup-win32-arm64-msvc": "4.12.0", - "@rollup/rollup-win32-ia32-msvc": "4.12.0", - "@rollup/rollup-win32-x64-msvc": "4.12.0", + "@rollup/rollup-android-arm-eabi": "4.14.0", + "@rollup/rollup-android-arm64": "4.14.0", + "@rollup/rollup-darwin-arm64": "4.14.0", + "@rollup/rollup-darwin-x64": "4.14.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.14.0", + "@rollup/rollup-linux-arm64-gnu": "4.14.0", + "@rollup/rollup-linux-arm64-musl": "4.14.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.14.0", + "@rollup/rollup-linux-riscv64-gnu": "4.14.0", + "@rollup/rollup-linux-s390x-gnu": "4.14.0", + "@rollup/rollup-linux-x64-gnu": "4.14.0", + "@rollup/rollup-linux-x64-musl": "4.14.0", + "@rollup/rollup-win32-arm64-msvc": "4.14.0", + "@rollup/rollup-win32-ia32-msvc": "4.14.0", + "@rollup/rollup-win32-x64-msvc": "4.14.0", "fsevents": "~2.3.2" } }, diff --git a/package.json b/package.json index c62d958..6cdafff 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@idotj/mastodon-embed-timeline", - "version": "4.3.12", + "version": "4.4.1", "description": "Displays Mastodon timeline with posts embed in your website. Very easy to setup, no dependencies, no trackers, cross-browser, WCAG compliant and fully responsive.", "license": "GNU", "author": { @@ -27,9 +27,6 @@ "main": "dist/mastodon-timeline.esm.js", "types": "dist/mastodon-timeline.d.ts", "style": "dist/mastodon-timeline.min.css", - "publishConfig": { - "registry": "https://gitlab.com/api/v4/projects/25689941/packages/npm/" - }, "scripts": { "test": "echo \"No test specified\" && exit 1", "build": "npm run build:minifyCss && rollup -c", @@ -37,7 +34,7 @@ }, "devDependencies": { "clean-css-cli": "^5.6.3", - "rollup": "^4.12.0", + "rollup": "^4.14.0", "@rollup/plugin-terser": "^0.4.4" }, "files": [ diff --git a/src/mastodon-timeline.css b/src/mastodon-timeline.css index e646565..f992569 100644 --- a/src/mastodon-timeline.css +++ b/src/mastodon-timeline.css @@ -1,4 +1,4 @@ -/* Mastodon embed timeline v4.3.12 */ +/* Mastodon embed timeline v4.4.1 */ /* More info at: */ /* https://gitlab.com/idotj/mastodon-embed-timeline */ diff --git a/src/mastodon-timeline.js b/src/mastodon-timeline.js index 6c7130f..9ec12a1 100644 --- a/src/mastodon-timeline.js +++ b/src/mastodon-timeline.js @@ -1,7 +1,7 @@ /** * Mastodon embed timeline * @author idotj - * @version 4.3.12 + * @version 4.4.1 * @url https://gitlab.com/idotj/mastodon-embed-timeline * @license GNU AGPLv3 */ @@ -38,6 +38,7 @@ export class Init { hideEmojos: false, btnShowContent: "SHOW CONTENT", hideVideoPreview: false, + btnPlayVideoTxt: "Load and play video", hidePreviewLink: false, previewMaxLines: "", hideCounterBar: false, @@ -82,7 +83,7 @@ export class Init { /** * Trigger callback when DOM loaded or completed - * @param {function} c Callback executed + * @param {Function} c Callback executed */ #onDOMContentLoaded(c) { if (typeof document !== "undefined" && document.readyState === "complete") { @@ -165,7 +166,7 @@ export class Init { /** * Apply the color theme in the timeline - * @param {string} themeType Type of color theme ('light' or 'dark') + * @param {String} themeType Type of color theme ('light' or 'dark') */ mtColorTheme(themeType) { this.#onDOMContentLoaded(() => { @@ -193,7 +194,7 @@ export class Init { /** * Requests to the server to collect all the data - * @returns {object} Data container + * @returns {Object} Data container */ #getTimelineData() { return new Promise((resolve, reject) => { @@ -256,8 +257,8 @@ export class Init { /** * Set all urls before fetching the data - * @param {string} Instance url api - * @returns {object} + * @param {String} Instance url api + * @returns {Object} */ #setUrls(i) { let urls = {}; @@ -301,9 +302,9 @@ export class Init { /** * Fetch data from server - * @param {string} u Url address to fetch - * @param {boolean} h gets the link header - * @returns {array} List of objects + * @param {String} u Url address to fetch + * @param {Boolean} h gets the link header + * @returns {Array} List of objects */ async #fetchData(u, h = false) { const response = await fetch(u); @@ -326,7 +327,7 @@ export class Init { /** * Check if there are enough posts to reach the value of maxNbPostFetch - * @returns {boolean} + * @returns {Boolean} */ #isNbPostsFulfilled() { return ( @@ -352,8 +353,8 @@ export class Init { /** * Parse link header into an object - * @param {string} l Link header - * @returns {object} + * @param {String} l Link header + * @returns {Object} */ #parseLinkHeader(l) { const linkArray = l.split(", ").map((header) => header.split("; ")); @@ -367,7 +368,7 @@ export class Init { /** * Filter all fetched posts and append them on the timeline - * @param {string} t Type of build (new or reload) + * @param {String} t Type of build (new or reload) */ async #buildTimeline(t) { await this.#getTimelineData(); @@ -446,7 +447,7 @@ export class Init { /** * Add the attribute Aria-setsize to all posts - * @param {number} n The total number of posts showed in the timeline + * @param {Number} n The total number of posts showed in the timeline */ #addAriaSetsize(n) { const articles = this.mtBodyNode.getElementsByTagName("article"); @@ -458,8 +459,8 @@ export class Init { /** * Add each post in the timeline container - * @param {object} c Post content - * @param {number} i Index of post + * @param {Object} c Post content + * @param {Number} i Index of post */ #appendPost(c, i) { this.mtBodyNode.insertAdjacentHTML("beforeend", this.#assamblePost(c, i)); @@ -467,8 +468,8 @@ export class Init { /** * Build post structure - * @param {object} c Post content - * @param {number} i Index of post + * @param {Object} c Post content + * @param {Number} i Index of post */ #assamblePost(c, i) { let avatar, @@ -641,7 +642,7 @@ export class Init { if (c.spoiler_text !== "") { content = '
    ' + - c.spoiler_text + + this.#formatPostText(c.spoiler_text) + ' " + @@ -656,7 +657,7 @@ export class Init { ) { content = '
    ' + - c.reblog.spoiler_text + + this.#formatPostText(c.reblog.spoiler_text) + ' " + @@ -757,7 +758,7 @@ export class Init { "
    "; } - // Add all to main post container + // Put all elements together in the post container const post = '
    elements + * @param {Node} html The HTML + */ + function removeScripts(html) { + let scripts = html.querySelectorAll("script"); + for (let script of scripts) { + script.remove(); + } + } + + /** + * Check if the attribute is potentially dangerous + * @param {String} name The attribute name + * @param {String} value The attribute value + * @return {Boolean} If true, the attribute is potentially dangerous + */ + function isPossiblyDangerous(name, value) { + let val = value.replace(/\s+/g, "").toLowerCase(); + if (["src", "href", "xlink:href"].includes(name)) { + if (val.includes("javascript:") || val.includes("data:")) return true; + } + if (name.startsWith("on")) return true; + } + + /** + * Remove potentially dangerous attributes from an element + * @param {Node} elem The element + */ + function removeAttributes(elem) { + // Loop through each attribute + // If it's dangerous, remove it + let atts = elem.attributes; + for (let { name, value } of atts) { + if (!isPossiblyDangerous(name, value)) continue; + elem.removeAttribute(name); + } + } + + /** + * Remove dangerous stuff from the HTML document's nodes + * @param {Node} html The HTML document + */ + function clean(html) { + let nodes = html.children; + for (let node of nodes) { + removeAttributes(node); + clean(node); + } + } + + // Convert the string to HTML + let html = stringToHTML(); + + // Sanitize it + removeScripts(html); + clean(html); + + // If the user wants HTML nodes back, return them + // Otherwise, pass a sanitized string back + return n ? html.childNodes : html.innerHTML; + } + /** * Handle text changes made to posts - * @param {string} c Text content - * @returns {string} Text content modified + * @param {String} c Text content + * @returns {String} Text content modified */ #formatPostText(c) { let content = c; + // Sanitize string + content = this.#cleanHTML(content, false); + // Format hashtags and mentions content = this.#addTarget2hashtagMention(content); @@ -811,8 +896,8 @@ export class Init { /** * Add target="_blank" to all #hashtags and @mentions in the post - * @param {string} c Text content - * @returns {string} Text content modified + * @param {String} c Text content + * @returns {String} Text content modified */ #addTarget2hashtagMention(c) { let content = c.replaceAll('rel="tag"', 'rel="tag" target="_blank"'); @@ -826,12 +911,12 @@ export class Init { /** * Find all start/end and replace them by another start/end - * @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 + * @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 */ #replaceHTMLtag( c, @@ -855,8 +940,8 @@ export class Init { /** * Escape quotes and other special characters, to make them safe to add * to HTML content and attributes as plain text - * @param {string} s String - * @returns {string} String + * @param {String} s String + * @returns {String} String */ #escapeHTML(s) { return (s ?? "") @@ -869,9 +954,9 @@ export class Init { /** * Find all custom emojis shortcode and replace by image - * @param {string} c Text content - * @param {array} e List with all custom emojis - * @returns {string} Text content modified + * @param {String} c Text content + * @param {Array} e List with all custom emojis + * @returns {String} Text content modified */ #shortcode2Emojos(c, e) { if (c.includes(":")) { @@ -891,8 +976,8 @@ export class Init { /** * Format date - * @param {string} d Date in ISO format (YYYY-MM-DDTHH:mm:ss.sssZ) - * @returns {string} Date formated + * @param {String} d Date in ISO format (YYYY-MM-DDTHH:mm:ss.sssZ) + * @returns {String} Date formated */ #formatDate(d) { const originalDate = new Date(d); @@ -907,9 +992,9 @@ export class Init { /** * Create media element - * @param {object} m Media content - * @param {boolean} s Sensitive/spoiler status - * @returns {string} Media in HTML format + * @param {Object} m Media content + * @param {Boolean} s Sensitive/spoiler status + * @returns {String} Media in HTML format */ #createMedia(m, s = false) { const type = m.type; @@ -1019,7 +1104,9 @@ export class Init { '" alt="' + (m.description ? this.#escapeHTML(m.description) : "") + '" loading="lazy" />' + - '' + + '' + "
    "; } else { media = @@ -1051,8 +1138,8 @@ export class Init { /** * Open a dialog/modal with the styles of Mastodon timeline - * @param {string} i Dialog Id name - * @param {string} c Dialog HTML content + * @param {String} i Dialog Id name + * @param {String} c Dialog HTML content */ #openDialog(i, c) { let dialog = document.createElement("dialog"); @@ -1069,7 +1156,7 @@ export class Init { /** * Build a carousel/lightbox with the media content in the post clicked - * @param {event} e User interaction trigger + * @param {Event} e User interaction trigger */ #showCarousel(e) { // List all medias in the post and remove sensitive/spoiler medias @@ -1181,8 +1268,8 @@ export class Init { /** * Add interactions for the carousel - * @param {number} t Total number of medias loaded - * @param {number} m Index position of media clicked by user + * @param {Number} t Total number of medias loaded + * @param {Number} m Index position of media clicked by user */ #setCarouselInteractions(t, m) { let currentMediaIndex = m; @@ -1270,7 +1357,7 @@ export class Init { /** * Replace the video preview image by the video player - * @param {event} e User interaction trigger + * @param {Event} e User interaction trigger */ #loadPostVideo(e) { const parentNode = e.target.closest("[data-media-type]"); @@ -1281,7 +1368,7 @@ export class Init { /** * Spoiler toggle for text - * @param {event} e User interaction trigger + * @param {Event} e User interaction trigger */ #toogleTxtSpoiler(e) { const target = e.target; @@ -1302,7 +1389,7 @@ export class Init { /** * Spoiler toggle for image/video - * @param {event} e User interaction trigger + * @param {Event} e User interaction trigger */ #toogleMediaSpoiler(e) { const target = e.target; @@ -1315,8 +1402,8 @@ export class Init { /** * Create preview link - * @param {object} c Preview link content - * @returns {string} Preview link in HTML format + * @param {Object} c Preview link content + * @returns {String} Preview link in HTML format */ #createPreviewLink(c) { let previewDescription = ""; @@ -1369,8 +1456,8 @@ export class Init { /** * Parse HTML string - * @param {string} s HTML string - * @returns {string} Plain text + * @param {String} s HTML string + * @returns {String} Plain text */ #parseHTMLstring(s) { const parser = new DOMParser(); @@ -1443,6 +1530,7 @@ export class Init { */ #addPostListener() { this.mtBodyNode.addEventListener("click", (e) => { + console.log("click on: ", e); const target = e.target; const localName = target.localName; const parentNode = target.parentNode; @@ -1487,6 +1575,7 @@ export class Init { (parentNode.getAttribute("data-media-type") === "video" || parentNode.getAttribute("data-media-type") === "gifv")) ) { + console.log("loadPostVideo"); this.#loadPostVideo(e); } }); @@ -1501,7 +1590,7 @@ export class Init { /** * Open post in a new tab/page avoiding any other natural link - * @param {event} e User interaction trigger + * @param {Event} e User interaction trigger */ #openPostUrl(e) { const urlPost = e.target.closest(".mt-post").dataset.location; @@ -1545,8 +1634,8 @@ export class Init { /** * Show an error on the timeline - * @param {string} e Error message - * @param {string} i Icon + * @param {String} e Error message + * @param {String} i Icon */ #showError(t, i) { const icon = i || "❌";