summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--content/mastodon.md9
-rw-r--r--content/posts/weekly/5.md112
-rw-r--r--layouts/partials/custom_head.html6
-rw-r--r--layouts/partials/style.html18
-rw-r--r--layouts/shortcodes/mastodon.html16
-rw-r--r--static/css/mastodon-timeline.min.css650
-rw-r--r--static/js/mastodon-timeline.esm.js949
7 files changed, 1752 insertions, 8 deletions
diff --git a/content/mastodon.md b/content/mastodon.md
new file mode 100644
index 0000000..9431934
--- /dev/null
+++ b/content/mastodon.md
@@ -0,0 +1,9 @@
+---
+title: 'Mastodon'
+date: '2026-01-11T20:09:03+08:00'
+slug:
+categories:
+tags:
+comments: false
+---
+{{< mastodon >}}
diff --git a/content/posts/weekly/5.md b/content/posts/weekly/5.md
new file mode 100644
index 0000000..204770e
--- /dev/null
+++ b/content/posts/weekly/5.md
@@ -0,0 +1,112 @@
+---
+date: '2026-01-11T17:52:03+08:00'
+title: '回声周刊 Vol.5:「相关」和「有关」的滥用'
+categories:
+ - 回声周刊
+issue: 5
+tags:
+ - 语言
+ - 中文
+ - 西西弗
+ - 村上春树
+draft: false
+---
+
+![](https://images.glowisle.me/)
+
+> 这里是回声周刊,分享我这周读过的文章书籍、近期发生的大事小事。每周日更新。如果你对这个周刊感兴趣,可以 [订阅周刊](/categories/%E5%9B%9E%E5%A3%B0%E5%91%A8%E5%88%8A/atom.xml) 或加入 [TG频道](https://t.me/glowisle)。
+
+这周我一直在忙期末复习,因此本期周刊内容很少,最晚可能要到下下期恢复正常量。
+
+## 〰︎ 声波
+
+{{< music
+ cover="https://images.glowisle.me/b67616d00001e0274c732f8aa0e0ccbb3d17d96.jpeg"
+ title="From The Start"
+ artist=""
+ apple=""
+ netease=""
+ spotify="https://open.spotify.com/track/43iIQbw5hx986dUEZbr3eN?si=4c4d4d3680134d9b"
+>}}
+
+## 🕮 声源
+
+### 不要逃避解释:论「相关」和「有关」在日常写作中的滥用
+
+> 🔗 [文章链接](https://hsu.cy/2025/04/irrelevancy/)
+
+文章指出了中文里「相关」和「有关」被滥用的现象,进行了批判,并给出了减少使用这两个词的例子。
+
+文章里提到的最经典的例子「有关部门」,我觉得是刻意为之。这和「原则上不可以」就是「可以」的意味很相似,一定有人会说这是中文的博大精深,其实很多情况下就是故意含糊其辞的说法。不过这篇文章确实一针见血,能给写作者很大的启发。除了文章提到的两个词,我觉得在一些应试叙事文里「便」也被滥用了,它是「就」的书面语版本,表动作或事件之间的因果联系和连续性,也就是承接关系。我曾经读过我同学的一篇作文,事件就是买手抓饼身上没带钱,老板说没事可以不用给。他在那篇六百多字的文章里用了二三十个「便」。
+
+
+### 你分得清 Follow Feed 和 Subscribe 吗?
+
+今天在搜集RSS订阅源的时候思考了一个很常见的文件命名 —— `feed.xml`。
+
+为什么在表达「RSS订阅」这个意思的时候,不用常见的subscribe(订阅),或者follow(关注),而是用feed(v. 喂养;**供应** / n. 饲料)这个奇怪的词呢?
+
+#### subscribe
+
+来看看这个词在字典中的含义:
+
+> to pay money to an organization in order to receive a product, use a service regularly[^1]
+
+**向一个组织付费以收到产品**、**使用周期性的服务**、**主动选择,直接接收更新**如 email, push notifications(消息推送)。
+
+仔细一想,这是不是和**Newsletter**的模式很像?因此可以看到很多支持Newsletter的网站,会在提交邮箱的输入框旁边附上一个写着Subscribe的按钮。
+
+除了Newsletter,也适用于某些软件的会员服务,例如:
+
+> 2.3 million people subscribe to this online music service.
+>
+> 230 万人订阅了这项在线音乐服务。
+
+从词源学的角度看,subscribe的词根sub-(在下面)+scribe(写),原意是「在文件下方签名」。这其中包含着一种「我登记,你准时交付」的「契约感」
+
+所以,遇到这种情况,使用Subscribe是最好的选择。
+
+#### Follow
+
+这个单词多被用于社交媒体的「关注」按钮里,它的意思更多是"Publicly add someone to one's network(添加到公开网络)",以及"Show their posts up in main algorithm-dependent feed"(在关注界面,受算法影响地展示他们的帖子)。[^2]
+
+在我看来,这和subscribe的微妙的区别在于,subscribe的作用对象在大多数情况下是正式的、有质量保证「服务」,而follow是一种相对轻松的「帖子(post)」,聚焦在「关注个人」而非「订阅服务」。
+
+#### Feed
+
+最后在来看这个经常被使用于RSS订阅的feed的释义:
+
+> to supply something to a person or thing(向某人提供某物), or put something into a machine or system, especially in a regular or continuous way(将某物放入机器 / 系统,尤其指有规律的、连续的方式).
+
+这不是某个动作,而是目的,这是机器对机器的自动化喂养。它的结果是:获取一个由你自己筛选的源所供给的个性化信息流。因此这个词非常贴合RSS这个模式。
+
+这三个词的背后隐藏着用户主动权的变化,subscribe是一种「强主动」,点名要看,不能错过的需求;follow是像推特的关注那样「我想看,但有算法帮我推荐」的机制;feed在RSS盛行的时代也是一种用户完全掌控的强主动,但在现代APP里,又有了一层「投喂」的意思,例如TikTok和小红书的算法推荐。不得不说,「投喂」这个词实在是太难听了。
+
+## ♇ 所在
+
+### 以后再也不去线下店买书了
+
+这周六又去了一次西西弗,不过是以前没去过的店。因为很想看《1973年的弹子球》,又等不及网购
+
+举着豆瓣在书架绕了半天,那么一面墙的日本文学,有《且听风吟》,有《舞!舞!舞!》,就是没有我想要的那本。最后终于鼓起勇气跟店员说能不能帮我找一下,结果他们还没卖。
+
+就拿了一本《一个人的好天气》和《舞》结账,结果花了一百五十块。唉,真黑啊!
+
+![价值四块钱的袋子……](https://images.glowisle.me/https://images.glowisle.me/E4188D7B80E34C235C46CF10E0A5D4BE.jpg)
+
+
+我不知道怎么拒绝说我自己带包了不要袋子,花四块钱又买了一个袋子,这次出行唯一成功的就是悬崖勒马没有被她忽悠充值办卡……
+
+出来之后,看到有麦当劳,就进去一口气点了三个冰淇淋吃。
+
+![](https://images.glowisle.me/DFE6701348DB336E54AF1C611F34719A.jpg)
+
+![战况](https://images.glowisle.me/230710588BD53B2E3B9C6F79E38EE7B4.jpg)
+
+小时候求着父母才能偶尔成到一个的东西,现在一口气吃三个,爽!
+
+[^1]: [SUBSCRIBE - Cambridge Dictionary](https://dictionary.cambridge.org/zhs/%E8%AF%8D%E5%85%B8/%E8%8B%B1%E8%AF%AD-%E6%B1%89%E8%AF%AD-%E7%B9%81%E4%BD%93/subscribe)
+
+[^2]: [In a social app, do I choose "Subscribe" or "Follow"? - Quora](https://www.quora.com/In-a-social-app-do-I-choose-Subscribe-or-Follow)
+
+
diff --git a/layouts/partials/custom_head.html b/layouts/partials/custom_head.html
index 206ee0b..a485758 100644
--- a/layouts/partials/custom_head.html
+++ b/layouts/partials/custom_head.html
@@ -15,3 +15,9 @@
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
/>
+<link
+ rel="stylesheet"
+ href="https://cdn.jsdelivr.net/npm/@idotj/mastodon-embed-timeline@4.7.0/dist/mastodon-timeline.min.css"
+ integrity="sha256-Qi3H+bdH6RuMuyR1trAlG5bMWJGl9y3jPiTc1PWQFpI="
+ crossorigin="anonymous"
+/>
diff --git a/layouts/partials/style.html b/layouts/partials/style.html
index 50650ea..8429fdb 100644
--- a/layouts/partials/style.html
+++ b/layouts/partials/style.html
@@ -16,7 +16,7 @@
--code-bg-color: #f2f2f2;
--code-text-color: #222;
--blockquote-border-color: #666;
- --blockquote-text-color: #666;
+ --blockquote-text-color: #8c8c8c;
--upvoted-color: #fa8072;
--caption-text-color: #666;
--toc-text-color: #e5e5e5;
@@ -95,8 +95,9 @@
}
article p {
- line-height: 40px;
+ line-height: 35px;
margin-top: 20px;
+ font-weight: 550;
}
.item-link {
@@ -289,12 +290,17 @@
--music-bg-hover-dark: #525252; /* neutral-600 */
--music-bg-dark: rgba(82, 82, 82, 0.5); /* neutral-600/50 */
--music-text-secondary: #a3a3a3;
+ --color-background: var(--body-bg-color) !important;
}
blockquote strong {
font-weight: 600;
}
+ article p {
+ font-weight: 400;
+ }
+
.music-btn {
background-color: var(--music-bg-dark);
}
@@ -526,18 +532,14 @@
}
blockquote {
- border-left: 4px solid var(--blockquote-border-color);
+ /* border-left: 4px solid var(--blockquote-border-color); */
color: var(--blockquote-text-color);
margin: 0;
margin-top: 17px;
- padding-left: 16px;
+ padding-left: 20px;
font-style: normal;
}
- blockquote p {
- margin: 0;
- }
-
footer {
padding: 25px 0;
text-align: left;
diff --git a/layouts/shortcodes/mastodon.html b/layouts/shortcodes/mastodon.html
new file mode 100644
index 0000000..cfb15ca
--- /dev/null
+++ b/layouts/shortcodes/mastodon.html
@@ -0,0 +1,16 @@
+<script type="module" src="https://unpkg.com/mastodon-widget"></script>
+
+<mastodon-widget account="Verdant@c7.io" limit="10" header="true">
+</mastodon-widget>
+
+<style>
+ header,
+ .post-title,
+ .post-info {
+ display: none !important;
+ }
+
+ mastodon-weight {
+ background-color: black !important;
+ }
+</style>
diff --git a/static/css/mastodon-timeline.min.css b/static/css/mastodon-timeline.min.css
new file mode 100644
index 0000000..ec8493e
--- /dev/null
+++ b/static/css/mastodon-timeline.min.css
@@ -0,0 +1,650 @@
+.mt-container,
+.mt-container[data-theme="light"],
+.mt-dialog,
+.mt-dialog[data-theme="light"] {
+ --mt-txt-max-lines: none;
+ --mt-preview-max-lines: none;
+ --mt-color-bg: #fff;
+ --mt-color-bg-hover: #e9eef1;
+ --mt-color-line-gray: #c0cdd9;
+ --mt-color-contrast-gray: #606984;
+ --mt-color-content-txt: #000;
+ --mt-color-hashtag: #d7e1e9;
+ --mt-color-link: #3a3bff;
+ --mt-color-error-txt: #8b0000;
+ --mt-color-btn-bg: #6364ff;
+ --mt-color-btn-bg-hover: #563acc;
+ --mt-color-btn-txt: #fff;
+ --mt-color-backdrop: #00000090;
+ --mt-color-placeholder: #60698425;
+}
+.mt-container[data-theme="dark"],
+.mt-dialog[data-theme="dark"] {
+ --mt-color-bg: #181821;
+ --mt-color-bg-hover: #21232c;
+ --mt-color-line-gray: #393f4f;
+ --mt-color-contrast-gray: #606984;
+ --mt-color-content-txt: #fff;
+ --mt-color-hashtag: #292c38;
+ --mt-color-link: #8c8dff;
+ --mt-color-error-txt: #fe6c6c;
+}
+.mt-container button,
+.mt-dialog button {
+ font: inherit;
+}
+.mt-container a,
+.mt-container button,
+.mt-dialog button {
+ cursor: pointer;
+}
+.mt-container {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ overflow-y: auto;
+ position: relative;
+ background-color: var(--mt-color-bg);
+ scrollbar-color: var(--mt-color-contrast-gray) var(--mt-color-bg);
+ scrollbar-width: auto;
+ container: mt-container/inline-size;
+}
+.mt-container::-webkit-scrollbar {
+ width: 0.25rem;
+ height: 0.25rem;
+}
+.mt-container::-webkit-scrollbar-thumb {
+ background-color: var(--mt-color-contrast-gray);
+ border: none;
+ border-radius: 3rem;
+}
+.mt-container::-webkit-scrollbar-thumb:active,
+.mt-container::-webkit-scrollbar-thumb:hover {
+ background-color: var(--mt-color-contrast-gray);
+}
+.mt-container::-webkit-scrollbar-track {
+ background-color: var(--mt-color-bg);
+ border: none;
+ border-radius: 0;
+}
+.mt-container::-webkit-scrollbar-corner,
+.mt-container::-webkit-scrollbar-track:active,
+.mt-container::-webkit-scrollbar-track:hover {
+ background-color: var(--mt-color-bg);
+}
+.mt-container a {
+ text-decoration: none;
+ color: var(--mt-color-link);
+}
+.mt-container a:not(.mt-post-preview):hover {
+ text-decoration: underline;
+}
+.mt-body {
+ padding: 1rem;
+ white-space: pre-wrap;
+ word-wrap: break-word;
+ margin-bottom: 1rem;
+}
+.mt-body .invisible {
+ font-size: 0;
+ line-height: 0;
+ display: inline-block;
+ width: 0;
+ height: 0;
+ position: absolute;
+}
+.mt-post {
+ margin: 0.25rem;
+ padding: 1rem 0.5rem;
+ position: relative;
+ min-height: 3.75rem;
+ background-color: transparent;
+ border-bottom: 1px solid var(--mt-color-line-gray);
+}
+.mt-post:focus,
+.mt-post:hover {
+ cursor: pointer;
+ background-color: var(--mt-color-bg-hover);
+}
+.mt-container:not(:has(.mt-footer)) .mt-post:last-child {
+ border-bottom: none;
+}
+.mt-post p:last-child {
+ margin-bottom: 0;
+}
+.mt-post-avatar {
+ margin-right: 0.75rem;
+}
+.mt-post-avatar-standard {
+ width: 2.25rem;
+ height: 2.25rem;
+}
+.mt-post-avatar-boosted {
+ width: 3rem;
+ height: 3rem;
+ position: relative;
+}
+.mt-post-avatar-image-big img,
+.mt-post-avatar-image-small img {
+ aspect-ratio: 1/1;
+ border-radius: 0.25rem;
+ overflow: hidden;
+}
+.mt-post-avatar-image-big img {
+ width: 2.25rem;
+ height: 2.25rem;
+}
+.mt-post-avatar-image-small img {
+ width: 1.5rem;
+ height: 1.5rem;
+ top: 1.5rem;
+ left: 1.5rem;
+ position: absolute;
+}
+.mt-post-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ margin-bottom: 1rem;
+}
+.mt-post-header-user {
+ overflow: hidden;
+ padding-right: 0.75rem;
+}
+.mt-post-header-user .mt-custom-emoji {
+ height: 1rem;
+ min-width: 1rem;
+ width: auto;
+}
+.mt-container .mt-post-header-user > a {
+ color: var(--mt-color-content-txt);
+ overflow-wrap: anywhere;
+}
+.mt-container .mt-post-header-user > a:hover {
+ text-decoration: none;
+}
+.mt-post-header-user-name {
+ font-weight: 600;
+}
+.mt-container .mt-post-header-user:hover .mt-post-header-user-name {
+ text-decoration: underline;
+}
+.mt-post-header-user-account {
+ display: block;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ color: var(--mt-color-contrast-gray);
+}
+.mt-post-header-date {
+ display: flex;
+ font-size: 0.75rem;
+ text-align: right;
+ margin-left: auto;
+}
+.mt-post-header-date .mt-post-pinned {
+ width: 1.25rem;
+ margin-top: -0.25rem;
+ fill: var(--mt-color-contrast-gray);
+}
+.mt-container .mt-post-header-date > a {
+ white-space: nowrap;
+ color: var(--mt-color-contrast-gray) !important;
+}
+.mt-post-txt {
+ margin-bottom: 1rem;
+ color: var(--mt-color-content-txt);
+}
+.mt-post-txt .spoiler-txt-hidden {
+ display: none;
+}
+.mt-post-txt.truncate {
+ display: -webkit-box;
+ overflow: hidden;
+ -webkit-line-clamp: var(--mt-txt-max-lines);
+ -webkit-box-orient: vertical;
+}
+.mt-post-txt:not(.truncate) .ellipsis::after {
+ content: "...";
+}
+.mt-post-txt blockquote {
+ border-left: 0.25rem solid var(--mt-color-line-gray);
+ margin-left: 0;
+ padding-left: 0.5rem;
+}
+.mt-post-txt .mt-custom-emoji {
+ height: 1.5rem;
+ min-width: 1.5rem;
+ margin-bottom: -0.25rem;
+ width: auto;
+}
+.mt-post-txt .mention.hashtag {
+ display: inline-block;
+ font-size: 0.8rem;
+ border-radius: 0.25rem;
+ background-color: var(--mt-color-hashtag);
+ padding: 0.25rem 0.5rem;
+ margin: 0 0.25rem 0.25rem 0;
+}
+.mt-post-txt .mention.hashtag:only-child {
+ margin: 0;
+}
+.mt-post-poll {
+ margin-bottom: 1rem;
+ color: var(--mt-color-content-txt);
+}
+.mt-post-poll ul {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+}
+.mt-post-poll ul li {
+ font-size: 0.9rem;
+ margin-bottom: 0.5rem;
+}
+.mt-post-poll.mt-post-poll-expired ul li {
+ color: var(--mt-color-contrast-gray);
+}
+.mt-post-poll ul li:not(:last-child) {
+ margin-bottom: 0.25rem;
+}
+.mt-post-poll ul li:before {
+ content: "◯";
+ padding-right: 0.5rem;
+}
+.mt-post-poll.mt-post-poll-expired ul li:before {
+ content: "";
+ padding-right: 0;
+}
+.mt-post-media-wrapper {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 0.5rem;
+ margin-bottom: 1rem;
+}
+.mt-post-media {
+ position: relative;
+ overflow: hidden;
+ width: 100%;
+}
+.mt-post-media-wrapper:has(> :last-child:nth-child(odd))
+ .mt-post-media:not(:only-child):first-child {
+ grid-column: 1/3;
+}
+.mt-post-media:only-child {
+ grid-column: 1/3;
+}
+.mt-post-media-spoiler > .mt-btn-play,
+.mt-post-media-spoiler > audio,
+.mt-post-media-spoiler > img,
+.mt-post-media-spoiler > video {
+ filter: blur(2rem);
+ pointer-events: none;
+}
+.mt-post-media,
+.mt-post-media-spoiler > audio,
+.mt-post-media-spoiler > img,
+.mt-post-media > img,
+.mt-post-media > video {
+ border-radius: 0.5rem;
+}
+.mt-post-media > audio {
+ width: 100%;
+ position: relative;
+ z-index: 1;
+}
+.mt-post-media > img,
+.mt-post-media > video {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ object-fit: cover;
+ text-align: center;
+ color: var(--mt-color-content-txt);
+ background-color: var(--mt-color-placeholder);
+}
+body:has(dialog.mt-dialog[open]) {
+ overflow: hidden;
+}
+.mt-dialog {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ height: 100%;
+ border: none;
+ color: var(--mt-color-content-txt);
+ background-color: transparent;
+ padding: 0;
+ margin: 1rem;
+ overflow: hidden;
+}
+.mt-dialog::backdrop {
+ background-color: var(--mt-color-backdrop);
+ backdrop-filter: blur(8px);
+ -webkit-backdrop-filter: blur(8px);
+}
+.mt-carousel-header {
+ position: absolute;
+ top: 0;
+ right: 0;
+ z-index: 2;
+}
+.mt-carousel-body {
+ width: 100%;
+ height: 100%;
+}
+.mt-carousel-scroll {
+ display: flex;
+ width: 100%;
+ height: 100%;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ overflow-x: auto;
+ overflow-y: hidden;
+ scroll-snap-type: x mandatory;
+ scroll-behavior: smooth;
+ scrollbar-width: none;
+}
+.mt-carousel-scroll::-webkit-scrollbar {
+ display: none;
+ -webkit-appearance: none;
+}
+.mt-carousel-item {
+ scroll-snap-align: center;
+ width: 100%;
+ height: 100%;
+}
+.mt-carousel-media-wrapper {
+ width: calc(100vw - 2.5rem);
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ text-align: center;
+}
+.mt-carousel-media {
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+ padding: 2rem;
+}
+.mt-carousel-next,
+.mt-carousel-prev {
+ position: absolute;
+ background-color: transparent;
+ border: none;
+ padding: 0.5rem;
+ z-index: 2;
+}
+.mt-carousel-prev {
+ left: 0;
+ padding-left: 0;
+}
+.mt-carousel-next {
+ right: 0;
+ padding-right: 0;
+}
+.mt-post-preview {
+ min-height: 4rem;
+ display: flex;
+ flex-direction: row;
+ border: 1px solid var(--mt-color-line-gray);
+ border-radius: 0.5rem;
+ color: var(--mt-color-link);
+ font-size: 0.8rem;
+ margin: 1rem 0;
+ overflow: hidden;
+}
+.mt-post-preview-image {
+ width: 40%;
+ align-self: stretch;
+}
+.mt-post-preview-image img {
+ display: block;
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ color: var(--mt-color-content-txt);
+}
+.mt-post-preview-noImage {
+ width: 40%;
+ font-size: 1.5rem;
+ align-self: center;
+ text-align: center;
+}
+.mt-post-preview-content {
+ width: 60%;
+ display: flex;
+ align-self: center;
+ flex-direction: column;
+ padding: 0.5rem 1rem;
+ gap: 0.5rem;
+}
+.mt-post-preview-content:has(.mt-post-preview-description.truncate) {
+ align-self: unset;
+}
+.mt-post-preview-description {
+ display: block;
+ color: var(--mt-color-contrast-gray);
+}
+.mt-post-preview-description.truncate {
+ display: -webkit-box;
+ overflow: hidden;
+ -webkit-line-clamp: var(--mt-preview-max-lines);
+ -webkit-box-orient: vertical;
+}
+.mt-post-preview-description:not(.truncate) .ellipsis::after {
+ content: "...";
+}
+.mt-post-preview-title {
+ font-weight: 600;
+}
+.mt-post-counter-bar {
+ display: flex;
+ min-width: 6rem;
+ max-width: 40rem;
+ justify-content: space-between;
+ color: var(--mt-color-contrast-gray);
+}
+.mt-post-counter-bar-favorites,
+.mt-post-counter-bar-reblog,
+.mt-post-counter-bar-replies {
+ display: flex;
+ font-size: 0.75rem;
+ gap: 0.25rem;
+ align-items: center;
+ opacity: 0.6;
+ cursor: default;
+}
+.mt-post-counter-bar-favorites > svg,
+.mt-post-counter-bar-reblog > svg,
+.mt-post-counter-bar-replies > svg {
+ width: 1rem;
+ fill: var(--mt-color-contrast-gray);
+}
+.mt-btn-play {
+ display: flex;
+ position: absolute;
+ width: 3rem;
+ height: 3rem;
+ top: calc(50% - 1.5rem);
+ left: calc(50% - 1.5rem);
+ justify-content: center;
+ align-items: center;
+ background-color: transparent;
+ border: none;
+ cursor: pointer;
+}
+.mt-btn-play > svg {
+ width: 2.5rem;
+ height: 2.5rem;
+ fill: var(--mt-color-bg);
+ stroke: var(--mt-color-content-txt);
+ stroke-width: 1px;
+}
+.mt-post-media.mt-loading-spinner .mt-btn-play {
+ display: none;
+}
+.mt-container .mt-btn-dark,
+.mt-dialog .mt-btn-dark {
+ display: flex;
+ border-radius: 0.25rem;
+ background-color: var(--mt-color-line-gray);
+ border: 0;
+ color: var(--mt-color-content-txt);
+ font-weight: 600;
+ font-size: 0.75rem;
+ text-align: center;
+ padding: 0.25rem 0.5rem;
+ line-height: 1.25rem;
+ vertical-align: top;
+}
+.mt-dialog .mt-btn-dark {
+ margin-left: auto;
+}
+.mt-container .mt-btn-violet,
+.mt-container a.mt-btn-violet,
+.mt-dialog .mt-btn-violet,
+.mt-dialog a.mt-btn-violet {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ border-radius: 0.25rem;
+ border: 0.5rem;
+ padding: 0.5rem 0.75rem;
+ font-size: 1rem;
+ font-weight: 600;
+ text-align: center;
+ background-color: var(--mt-color-btn-bg);
+ color: var(--mt-color-btn-txt);
+}
+.mt-container .mt-btn-violet:hover,
+.mt-container a.mt-btn-violet:hover,
+.mt-dialog .mt-btn-violet:hover,
+.mt-dialog a.mt-btn-violet:hover {
+ background-color: var(--mt-color-btn-bg-hover);
+ text-decoration: none;
+}
+.mt-post-txt .mt-btn-spoiler-txt {
+ display: inline-block;
+ vertical-align: middle;
+}
+.mt-post-media.mt-loading-spinner > .mt-btn-spoiler-media {
+ display: none;
+}
+.mt-post-media > .mt-btn-spoiler-media-show {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ z-index: 2;
+ transform: translate(-50%, -50%);
+}
+.mt-post-media.mt-post-media-spoiler > .mt-btn-spoiler-media-hide,
+.mt-post-media:not([data-media-width-hd]) > .mt-btn-spoiler-media-hide {
+ display: none;
+}
+.mt-post-media:not(.mt-post-media-spoiler) > .mt-btn-spoiler-media-show {
+ display: none;
+}
+.mt-post-media > .mt-btn-spoiler-media-hide {
+ position: absolute;
+ top: 0.5rem;
+ left: 0.5rem;
+ z-index: 2;
+}
+.mt-post-media > .mt-btn-spoiler-media-hide > svg {
+ fill: var(--mt-color-content-txt);
+ pointer-events: none;
+}
+.mt-error {
+ position: absolute;
+ left: 50%;
+ transform: translateX(-50%);
+ display: flex;
+ flex-direction: column;
+ height: calc(100% - 3.5rem);
+ width: calc(100% - 4.5rem);
+ justify-content: center;
+ align-items: center;
+ color: var(--mt-color-error-txt);
+ padding: 0.75rem;
+ text-align: center;
+}
+.mt-error-icon {
+ font-size: 2rem;
+ margin-bottom: 1rem;
+}
+.mt-error-message {
+ width: 100%;
+ padding: 1rem 0;
+}
+.mt-error-message hr {
+ color: var(--mt-color-line-gray);
+}
+.mt-body > .mt-loading-spinner {
+ position: absolute;
+ width: 3rem;
+ height: 3rem;
+ margin: auto;
+ top: calc(50% - 1.5rem);
+ right: calc(50% - 1.5rem);
+}
+.mt-loading-spinner {
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 128 128'%3E%3Cg%3E%3CanimateTransform attributeName='transform' type='rotate' from='0 64 64' to='360 64 64' dur='1000ms' repeatCount='indefinite'/%3E%3Cpath d='M64 6.69a57.3 57.3 0 1 1 0 114.61A57.3 57.3 0 0 1 6.69 64' fill='none' stroke='%23404040' stroke-width='12'/%3E%3C/g%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: center center;
+ background-color: transparent;
+ background-size: min(2.5rem, calc(100% - 0.5rem));
+}
+.mt-footer {
+ display: flex;
+ flex-flow: wrap;
+ margin: auto auto 2rem auto;
+ padding: 0 1rem;
+ gap: 1.5rem;
+ align-items: center;
+ justify-content: center;
+}
+.visually-hidden {
+ position: absolute !important;
+ width: 1px !important;
+ height: 1px !important;
+ padding: 0 !important;
+ margin: -1px !important;
+ overflow: hidden !important;
+ clip: rect(0, 0, 0, 0) !important;
+ white-space: nowrap !important;
+ border: 0 !important;
+}
+@supports (container-type: inline-size) {
+ @container mt-container (max-width:20rem) {
+ .mt-body {
+ padding: 0 0.5rem;
+ }
+ .mt-container .mt-post-header-date > a {
+ white-space: normal;
+ }
+ .mt-post-media-wrapper .mt-post-media {
+ grid-column: 1/3;
+ }
+ }
+}
+@supports not (container-type: inline-size) {
+ @media screen and (max-width: clamp(20rem,40rem,60rem)) {
+ .mt-body {
+ padding: 0 0.5rem;
+ }
+ .mt-container .mt-post-header-date > a {
+ white-space: normal;
+ }
+ .mt-post-media-wrapper .mt-post-media {
+ grid-column: 1/3;
+ }
+ }
+}
diff --git a/static/js/mastodon-timeline.esm.js b/static/js/mastodon-timeline.esm.js
new file mode 100644
index 0000000..da7473c
--- /dev/null
+++ b/static/js/mastodon-timeline.esm.js
@@ -0,0 +1,949 @@
+/**
+ * Mastodon embed timeline
+ * @author idotj
+ * @version 4.7.0
+ * @url https://gitlab.com/idotj/mastodon-embed-timeline
+ * @license GNU AGPLv3
+ */
+class t {
+ constructor(t = {}) {
+ ((this.defaultSettings = {
+ mtContainerId: "mt-container",
+ instanceUrl: "https://mastodon.social",
+ timelineType: "local",
+ userId: "",
+ profileName: "",
+ hashtagName: "",
+ spinnerClass: "mt-loading-spinner",
+ defaultTheme: "auto",
+ maxNbPostFetch: "20",
+ maxNbPostShow: "20",
+ dateFormatLocale: "en-GB",
+ dateFormatOptions: {
+ day: "2-digit",
+ month: "short",
+ year: "numeric",
+ },
+ hideUnlisted: !1,
+ hideReblog: !1,
+ hideReplies: !1,
+ hidePinnedPosts: !1,
+ hideUserAccount: !1,
+ txtMaxLines: "",
+ filterByLanguage: "",
+ 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 <
+ this.mtSettings.insistSearchContainerTime
+ ? requestAnimationFrame(i)
+ : console.error(
+ `Impossible to find the <div> 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 <div> 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",
+ '<div class="mt-loading-spinner"></div>',
+ ),
+ 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 <strong>instanceUrl</strong> value",
+ "⚠️",
+ ),
+ s = this.#r(i),
+ a = Object.entries(s).map(([t, i]) =>
+ this.#l(i, t)
+ .then((e) => ({ [t]: e }))
+ .catch(
+ (i) => (
+ e(
+ new Error(
+ "Something went wrong getting the timeline data.",
+ ),
+ ),
+ this.#n(i.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) {
+ const {
+ timelineType: e,
+ userId: i,
+ hashtagName: s,
+ hideReblog: a,
+ hideReplies: o,
+ maxNbPostFetch: n,
+ hidePinnedPosts: r,
+ hideEmojos: l,
+ } = this.mtSettings,
+ d = {};
+ switch (e) {
+ case "profile":
+ if (!i) {
+ this.#n(
+ "Please check your <strong>userId</strong> value",
+ "⚠️",
+ );
+ break;
+ }
+ ((d.timeline = `${t}accounts/${i}/statuses?limit=${n}`),
+ r || (d.pinned = `${t}accounts/${i}/statuses?pinned=true`));
+ break;
+ case "hashtag":
+ if (!s) {
+ this.#n(
+ "Please check your <strong>hashtagName</strong> value",
+ "⚠️",
+ );
+ break;
+ }
+ d.timeline = `${t}timelines/tag/${s}?limit=${n}`;
+ break;
+ case "local":
+ d.timeline = `${t}timelines/public?local=true&limit=${n}`;
+ break;
+ default:
+ this.#n(
+ "Please check your <strong>timelineType</strong> value",
+ "⚠️",
+ );
+ }
+ return (
+ a && (d.timeline += "&exclude_reblogs=true"),
+ o && (d.timeline += "&exclude_replies=true"),
+ l || (d.emojos = `${t}custom_emojis`),
+ d
+ );
+ }
+ async #l(t, e) {
+ const i = await fetch(t);
+ if (!i.ok)
+ throw new Error(
+ `\n Failed to fetch the following Url:<br />${t}<hr />Error status: ${i.status}<hr />Error message: ${i.statusText}\n `,
+ );
+ const s = await i.json();
+ return (
+ "timeline" === 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, "timeline")
+ .then((e) => {
+ ((this.fetchedData.timeline = [
+ ...this.fetchedData.timeline,
+ ...e,
+ ]),
+ t());
+ })
+ .catch(
+ (t) => (
+ reject(
+ new Error(
+ "Something went wrong fetching more posts.",
+ ),
+ ),
+ this.#n(t.message),
+ { [key]: [] }
+ ),
+ )
+ : 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 {
+ hideUnlisted: e,
+ maxNbPostShow: i,
+ filterByLanguage: s,
+ } = this.mtSettings,
+ a = this.fetchedData.timeline;
+ this.mtBodyNode.replaceChildren();
+ if (
+ (a
+ .filter((t) => {
+ const i =
+ "public" === t.visibility ||
+ (!e && "unlisted" === t.visibility),
+ a = t.language || (t.reblog ? t.reblog.language : null);
+ return i && ("" === s || a === s);
+ })
+ .forEach((t, e) => {
+ e < i && this.#c(t, e);
+ }),
+ "" !== this.mtBodyNode.innerHTML)
+ )
+ "newTimeline" === t
+ ? (this.#p(),
+ this.#g(),
+ this.#u(0),
+ this.#v(),
+ (this.mtSettings.btnSeeMore || this.mtSettings.btnReload) &&
+ this.#b())
+ : "updateTimeline" === t
+ ? this.#p()
+ : this.#n(
+ "The function buildTimeline() was expecting a param",
+ );
+ else {
+ const t = `No posts to show<hr />${a?.length || 0} posts have been fetched from the server<hr />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 < t; i++) e[i].setAttribute("aria-setsize", t);
+ }
+ #c(t, e) {
+ this.mtBodyNode.insertAdjacentHTML("beforeend", this.#w(t, e));
+ }
+ #w(t, e) {
+ const i = Boolean(t.reblog),
+ s = i ? t.reblog : t,
+ {
+ url: a,
+ created_at: o,
+ replies_count: n,
+ reblogs_count: r,
+ favourites_count: l,
+ } = s,
+ {
+ avatar: d,
+ url: m,
+ username: h,
+ display_name: c,
+ emojis: p,
+ } = s.account,
+ g =
+ '<a href="' +
+ m +
+ '" class="mt-post-avatar" rel="nofollow noopener noreferrer" target="_blank"><div class="mt-post-avatar-' +
+ (i ? "boosted" : "standard") +
+ '"><div class="mt-post-avatar-image-big mt-loading-spinner"><img src="' +
+ d +
+ '" alt="' +
+ this.#f(h) +
+ ' avatar" loading="lazy" /></div>' +
+ (i
+ ? '<div class="mt-post-avatar-image-small"><img src="' +
+ t.account.avatar +
+ '" alt="' +
+ this.#f(t.account.username) +
+ ' avatar" loading="lazy" /></div>'
+ : "") +
+ "</div></a>",
+ u =
+ '<div class="mt-post-header-user"><a href="' +
+ m +
+ '" rel="nofollow noopener noreferrer" target="_blank"><bdi class="mt-post-header-user-name">' +
+ (!this.mtSettings.hideEmojos && c ? this.#x(c, p) : c || h) +
+ "</bdi>" +
+ (this.mtSettings.hideUserAccount
+ ? ""
+ : '<br /><span class="mt-post-header-user-account">@' +
+ h +
+ "@" +
+ new URL(m).hostname +
+ "</span>") +
+ "</a></div>",
+ v = this.#S(o),
+ b =
+ '<div class="mt-post-header-date">' +
+ (t.pinned
+ ? '<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24" class="mt-post-pinned" aria-hidden="true"><path d="m640-480 80 80v80H520v240l-40 40-40-40v-240H240v-80l80-80v-280h-40v-80h400v80h-40v280Zm-286 80h252l-46-46v-314H400v314l-46 46Zm126 0Z"></path></svg>'
+ : "") +
+ '<a href="' +
+ a +
+ '" rel="nofollow noopener noreferrer" target="_blank"><time datetime="' +
+ o +
+ '">' +
+ v +
+ "</time>" +
+ (t.edited_at ? " *" : "") +
+ "</a></div>",
+ w =
+ this.mtSettings.hidePreviewLink || (!t.card && !t.reblog?.card)
+ ? ""
+ : this.#y(i ? t.reblog.card : t.card),
+ f =
+ "0" !== this.mtSettings.txtMaxLines &&
+ this.mtSettings.txtMaxLines.length
+ ? " truncate"
+ : "";
+ let x = "";
+ const S = s.spoiler_text ? s.spoiler_text : s.content,
+ y =
+ ' <button type="button" class="mt-btn-dark mt-btn-spoiler-txt" aria-expanded="false">' +
+ this.mtSettings.btnShowMore +
+ '</button><div class="spoiler-txt-hidden">' +
+ this.#L(s.content) +
+ w +
+ "</div>";
+ S &&
+ (x =
+ '<div class="mt-post-txt' +
+ f +
+ '"><div class="mt-post-txt-wrapper">' +
+ this.#L(S) +
+ (s.spoiler_text ? y : "") +
+ "</div></div>");
+ const L = [
+ ...t.media_attachments,
+ ...(t.reblog?.media_attachments || []),
+ ]
+ .map((t) => this.#T(t, s.sensitive))
+ .join(""),
+ T = L ? `<div class="mt-post-media-wrapper">${L}</div>` : "",
+ N = t.poll
+ ? '<div class="mt-post-poll ' +
+ (t.poll.expired ? "mt-post-poll-expired" : "") +
+ '"><ul>' +
+ t.poll.options
+ .map(function (t) {
+ return "<li>" + t.title + "</li>";
+ })
+ .join("") +
+ "</ul></div>"
+ : "",
+ M = this.mtSettings.hideCounterBar
+ ? ""
+ : '<div class="mt-post-counter-bar">' +
+ this.#N("replies", n) +
+ this.#N("reblog", r) +
+ this.#N("favorites", l) +
+ "</div>";
+ return (
+ '<article class="mt-post" aria-posinset="' +
+ (e + 1) +
+ '" data-location="' +
+ a +
+ '" tabindex="0"><div class="mt-post-header">' +
+ g +
+ u +
+ b +
+ "</div>" +
+ x +
+ T +
+ N +
+ (s.spoiler_text ? "" : w) +
+ M +
+ "</article>"
+ );
+ }
+ #N(t, e) {
+ return `<div class="mt-post-counter-bar-${t}">${{ replies: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 -960 960 960" aria-hidden="true"><path d="M774.913-185.869V-356q0-56.609-35.891-92.5-35.892-35.891-92.5-35.891H258.045L411.435-331l-56 56.566L105.869-524l249.566-249.566 56 56.566-153.39 153.391h388.477q88.957 0 148.566 59.609 59.608 59.609 59.608 148v170.131h-79.783Z"></path></svg>', reblog: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 -960 960 960" aria-hidden="true"><path d="M276.043-65.304 105.869-236.043l170.174-170.175 52.74 54.175-78.652 78.652h449.304v-160h75.261v235.261H250.131l78.652 78.087-52.74 54.74Zm-90.174-457.348v-235.261h524.565L631.782-836l52.74-54.74L854.696-720 684.522-549.26 631.782-604l78.652-78.652H261.13v160h-75.261Z"></path></svg>', favorites: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 -960 960 960" aria-hidden="true"><path d="m330.955-216.328 149.066-89 149.066 90.023-40.305-168.391 131.217-114.347-172.956-14.87L480-671.869l-67.043 158.521-172.956 14.305 131.427 113.796-40.473 168.919ZM212.086-50.608l70.652-305.305L45.52-561.305l312.645-26.579L480-876.176l121.835 288.292 312.645 26.579-237.218 205.392 71.217 305.306L480-213.173 212.086-50.607ZM480-433.87Z"></path></svg>' }[t]}${e}</div>`;
+ }
+ #M(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
+ );
+ }
+ #L(t) {
+ let e = t;
+ return (
+ (e = this.#M(e, !1)),
+ (e = this.#k(e)),
+ this.mtSettings.hideEmojos ||
+ (e = this.#x(e, this.fetchedData.emojos)),
+ this.mtSettings.markdownBlockquote &&
+ (e = this.#C(
+ e,
+ "<p>&gt;",
+ "</p>",
+ "<blockquote><p>",
+ "</p></blockquote>",
+ )),
+ e
+ );
+ }
+ #k(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
+ );
+ }
+ #C(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("&", "&amp;")
+ .replaceAll("<", "&lt;")
+ .replaceAll(">", "&gt;")
+ .replaceAll('"', "&quot;")
+ .replaceAll("'", "&#039;");
+ }
+ #x(t, e) {
+ if (t.includes(":")) {
+ for (const i of e) {
+ const e = new RegExp(`\\:${i.shortcode}\\:`, "g");
+ t = t.replace(
+ e,
+ `<img src="${i.url}" class="mt-custom-emoji" alt="Emoji ${i.shortcode}" />`,
+ );
+ }
+ return t;
+ }
+ return t;
+ }
+ #S(t) {
+ const e = new Date(t);
+ return new Intl.DateTimeFormat(
+ this.mtSettings.dateFormatLocale,
+ this.mtSettings.dateFormatOptions,
+ ).format(e);
+ }
+ #T(t, e = !1) {
+ const { type: i, url: s, preview_url: a, description: o, meta: n } = t,
+ { original: r, small: l } = n,
+ {
+ spinnerClass: d,
+ btnShowContent: m,
+ btnPlayVideoTxt: h,
+ hideVideoPreview: c,
+ } = this.mtSettings,
+ p =
+ '<button class="mt-btn-dark mt-btn-spoiler-media mt-btn-spoiler-media-hide"><svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24" class="icon icon-eye-slash" aria-hidden="true"><path d="m644-428-58-58q9-47-27-88t-93-32l-58-58q17-8 34.5-12t37.5-4q75 0 127.5 52.5T660-500q0 20-4 37.5T644-428Zm128 126-58-56q38-29 67.5-63.5T832-500q-50-101-143.5-160.5T480-720q-29 0-57 4t-55 12l-62-62q41-17 84-25.5t90-8.5q151 0 269 83.5T920-500q-23 59-60.5 109.5T772-302Zm20 246L624-222q-35 11-70.5 16.5T480-200q-151 0-269-83.5T40-500q21-53 53-98.5t73-81.5L56-792l56-56 736 736-56 56ZM222-624q-29 26-53 57t-41 67q50 101 143.5 160.5T480-280q20 0 39-2.5t39-5.5l-36-38q-11 3-21 4.5t-21 1.5q-75 0-127.5-52.5T300-500q0-11 1.5-21t4.5-21l-84-82Zm319 93Zm-151 75Z"></path></svg></button><button class="mt-btn-dark mt-btn-spoiler-media mt-btn-spoiler-media-show">' +
+ m +
+ "</button>",
+ g =
+ ' class="mt-post-media ' +
+ (e ? "mt-post-media-spoiler " : "") +
+ (d || "") +
+ '" data-media-type="' +
+ i +
+ '" data-media-url-hd="' +
+ s +
+ '"' +
+ (o ? ' data-media-alt-txt="' + this.#f(o) + '"' : "") +
+ ' data-media-width-hd="' +
+ r.width +
+ '" data-media-height-hd="' +
+ r.height +
+ '" style="padding-top: calc(100%/' +
+ l?.aspect +
+ ')">';
+ return "image" === i
+ ? "<div" +
+ g +
+ (e ? p : "") +
+ '<img src="' +
+ a +
+ '" alt="' +
+ (o ? this.#f(o) : "") +
+ '" loading="lazy" /></div>'
+ : "audio" === i
+ ? '<div class="mt-post-media ' +
+ (e ? "mt-post-media-spoiler " : "") +
+ (a ? d : "") +
+ '" data-media-type="audio">' +
+ (e ? p : "") +
+ '<audio controls src="' +
+ s +
+ '"></audio>' +
+ (a
+ ? '<img src="' +
+ a +
+ '" alt="' +
+ (o ? this.#f(o) : "") +
+ '" loading="lazy" />'
+ : "") +
+ "</div>"
+ : "video" === i || "gifv" === i
+ ? c
+ ? "<div" +
+ g +
+ (e ? p : "") +
+ '<video controls src="' +
+ s +
+ '" loop></video></div>'
+ : "<div" +
+ g +
+ (e ? p : "") +
+ '<img src="' +
+ a +
+ '" alt="' +
+ (o ? this.#f(o) : "") +
+ '" loading="lazy" /><button class="mt-btn-play" title="' +
+ h +
+ '"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 14"><path d="M9.5 7l-9 6.3V.7z"/></svg></button></div>'
+ : "";
+ }
+ #P(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 <video controls src="${t.getAttribute("data-media-url-hd")}" width="${t.getAttribute("data-media-width-hd")}" height="${t.getAttribute("data-media-height-hd")}" class="mt-carousel-media" style="max-width:${t.getAttribute("data-media-width-hd")}px; max-height:${t.getAttribute("data-media-height-hd")}px" loop>\n </video>\n `
+ : `\n <img src="${t.getAttribute("data-media-url-hd")}" width="${t.getAttribute("data-media-width-hd")}" height="${t.getAttribute("data-media-height-hd")}" class="mt-carousel-media mt-loading-spinner" alt="${t.getAttribute("data-media-alt-txt")}" style="max-width:${t.getAttribute("data-media-width-hd")}px; max-height:${t.getAttribute("data-media-height-hd")}px" dragabble="false" />\n `;
+ const a = `\n <li class="mt-carousel-item">\n <div id="mt-carousel-${e + 1}" class="mt-carousel-media-wrapper" data-media-type="${t.getAttribute("data-media-type")}">\n ${i}\n </div>\n </li>\n `;
+ s.push(a);
+ });
+ const a = `\n <div class="mt-carousel-header">\n <form method="dialog">\n <button id="mt-carousel-close" class="mt-btn-dark" title="${this.mtSettings.carouselCloseTxt}">\n <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">\n <path fill-rule="evenodd" d="M5.293 5.293a1 1 0 0 1 1.414 0L12 10.586l5.293-5.293a1 1 0 0 1 1.414 1.414L13.414 12l5.293 5.293a1 1 0 0 1-1.414 1.414L12 13.414l-5.293 5.293a1 1 0 0 1-1.414-1.414L10.586 12 5.293 6.707a1 1 0 0 1 0-1.414z" fill="var(--mt-color-content-txt)"/>\n </svg>\n </button>\n </form>\n </div>\n\n <div class="mt-carousel-body">\n <ul id="mt-carousel-scroll" class="mt-carousel-scroll">\n ${s.join("")}\n </ul>\n </div>\n\n <button id="mt-carousel-prev" class="mt-carousel-prev" title="${this.mtSettings.carouselPrevTxt}" ${1 === i ? "hidden" : ""}>\n <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24" aria-hidden="true">\n <path d="M560-240 320-480l240-240 56 56-184 184 184 184-56 56Z" fill="var(--mt-color-content-txt)"></path>\n </svg>\n </button>\n\n <button id="mt-carousel-next" class="mt-carousel-next" title="${this.mtSettings.carouselNextTxt}" ${i === e.length ? "hidden" : ""}>\n <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24" aria-hidden="true">\n <path d="M504-480 320-664l56-56 240 240-240 240-56-56 184-184Z" fill="var(--mt-color-content-txt)"></path>\n </svg>\n </button>\n `;
+ (this.#P("mt-carousel", a), s.length >= 2 && this.#$(e.length, i));
+ }
+ #$(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));
+ };
+ }
+ #B(t) {
+ const e = t.target.closest("[data-media-type]"),
+ i = e.dataset.mediaUrlHd;
+ (e.replaceChildren(),
+ (e.innerHTML = `<video controls src="${i}" autoplay loop></video>`));
+ }
+ #A(t) {
+ const e = t.target,
+ i = e.nextSibling,
+ s = "true" === e.getAttribute("aria-expanded");
+ (i.classList.toggle("spoiler-txt-hidden", s),
+ i.classList.toggle("spoiler-txt-visible", !s),
+ e.setAttribute("aria-expanded", !s),
+ (e.textContent = s
+ ? this.mtSettings.btnShowMore
+ : this.mtSettings.btnShowLess));
+ }
+ #H(t) {
+ const e = t.target;
+ e.parentNode.classList.toggle(
+ "mt-post-media-spoiler",
+ !e.classList.contains("mt-btn-spoiler-media-show"),
+ );
+ }
+ #y(t) {
+ const {
+ url: e,
+ image: i,
+ image_description: s,
+ provider_name: a,
+ title: o,
+ description: n,
+ author_name: r,
+ } = t,
+ { previewMaxLines: l, spinnerClass: d } = this.mtSettings;
+ let m = "";
+ return (
+ "0" !== l &&
+ n &&
+ (m =
+ '<span class="mt-post-preview-description' +
+ (0 !== l.length ? " truncate" : "") +
+ '">' +
+ this.#q(n) +
+ "</span>"),
+ '<a href="' +
+ e +
+ '" class="mt-post-preview" target="_blank" rel="noopener noreferrer">' +
+ (i
+ ? '<div class="mt-post-preview-image ' +
+ d +
+ '"><img src="' +
+ i +
+ '" alt="' +
+ this.#f(s) +
+ '" loading="lazy" /></div>'
+ : '<div class="mt-post-preview-noImage">📄</div>') +
+ '<div class="mt-post-preview-content">' +
+ (a
+ ? '<span class="mt-post-preview-provider">' +
+ this.#q(a) +
+ "</span>"
+ : "") +
+ '<span class="mt-post-preview-title">' +
+ o +
+ "</span>" +
+ m +
+ (r
+ ? '<span class="mt-post-preview-author">' +
+ this.#q(r) +
+ "</span>"
+ : "") +
+ "</div></a>"
+ );
+ }
+ #q(t) {
+ return new DOMParser().parseFromString(t, "text/html").body.textContent;
+ }
+ #b() {
+ const {
+ btnSeeMore: t,
+ btnReload: e,
+ timelineType: i,
+ profileName: s,
+ hashtagName: a,
+ instanceUrl: o,
+ } = this.mtSettings;
+ let n = "",
+ r = "";
+ if (t) {
+ let e = "";
+ switch (i) {
+ case "profile":
+ s
+ ? (e = s)
+ : this.#n(
+ "Please check your <strong>profileName</strong> value",
+ "⚠️",
+ );
+ break;
+ case "hashtag":
+ e = "tags/" + a;
+ break;
+ case "local":
+ e = "public/local";
+ }
+ n =
+ '<a class="mt-btn-violet btn-see-more" href="' +
+ o +
+ "/" +
+ this.#f(e) +
+ '" rel="nofollow noopener noreferrer" target="_blank">' +
+ t +
+ "</a>";
+ }
+ if (
+ (e &&
+ (r =
+ '<button class="mt-btn-violet btn-refresh"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"><path d="M21 3v5m0 0h-5m5 0l-3-2.708C16.408 3.867 14.305 3 12 3a9 9 0 1 0 0 18c4.283 0 7.868-2.992 8.777-7" stroke="var(--mt-color-btn-txt)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>' +
+ e +
+ "</button>"),
+ this.mtBodyNode.parentNode.insertAdjacentHTML(
+ "beforeend",
+ '<div class="mt-footer">' + n + r + "</div>",
+ ),
+ e)
+ ) {
+ const t = this.mtContainerNode.querySelector(".btn-refresh");
+ t && t.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.#D(t),
+ e.classList.contains("mt-btn-spoiler-txt") && this.#A(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")))) &&
+ this.#B(t));
+ }),
+ this.mtBodyNode.addEventListener("keydown", (t) => {
+ const e = t.target.localName;
+ "Enter" === t.key && "article" == e && this.#D(t);
+ }));
+ }
+ #D(t) {
+ const e = t.target.closest(".mt-post")?.dataset.location;
+ if (!e) return;
+ const i = t.target.localName;
+ if (
+ "a" === i ||
+ "span" === i ||
+ "button" === i ||
+ "bdi" === i ||
+ "time" === i
+ )
+ return;
+ const s = t.target.className;
+ if ("mt-post-media-spoiler" === s || "mt-post-preview-noImage" === s)
+ return;
+ const a = t.target.parentNode?.className;
+ "mt-post-avatar-image-big" !== a &&
+ "mt-post-avatar-image-small" !== a &&
+ "mt-post-header-user-name" !== a &&
+ "mt-post-preview-image" !== a &&
+ "mt-post-preview" !== a &&
+ 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 <div class="mt-error">\n <span class="mt-error-icon">${i}</span>\n <strong>Oops, something's happened:</strong>\n <div class="mt-error-message">${t}</div>\n </div>`),
+ this.mtBodyNode.setAttribute("role", "none"),
+ new Error(
+ "Stopping the script due to an error building the timeline.",
+ )
+ );
+ }
+}
+export { t as Init };