Merge remote-tracking branch 'cheeaun/main' into feature/paste-attach

# Conflicts:
#	src/locales/en.po
This commit is contained in:
Stefano Pigozzi 2024-10-11 02:03:27 +02:00
commit 6bd3ad0143
No known key found for this signature in database
GPG key ID: 5ADA3868646C3FC0
50 changed files with 2902 additions and 2708 deletions

View file

@ -7,6 +7,7 @@ on:
jobs:
auto-pull-request:
if: github.repository == 'cheeaun/phanpy'
runs-on: ubuntu-latest
steps:
- uses: vsoch/pull-request-action@master

View file

@ -14,9 +14,6 @@ jobs:
with:
node-version: 20
- run: npm ci
- uses: creyD/prettier_action@v4.3
with:
dry: true
# Don't write anything
prettier_options: '--check --config .prettierrc'
file_pattern: '.'
- run: |
echo "Prettier-ing files"
npx prettier "src/**/*.{js,jsx}" --check

View file

@ -319,7 +319,7 @@ Costs involved in running and developing this web app:
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/15791971/medium/88bdda3090339f16f6083390d32bb434_default.png" alt="" width="16" height="16" /> katullo11 (Italian)
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/14677260/medium/e53420d200961f48602324e18c091bdc.png" alt="" width="16" height="16" /> Kytta (German)
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/16529521/medium/ae6add93a901b0fefa2d9b1077920d73.png" alt="" width="16" height="16" /> llun (Thai)
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/16291756/medium/e1c4210f15537394cc764b8bc2dffe37.jpg" alt="" width="16" height="16" /> lucasofchirst (Occitan, Portuguese, Portuguese, Brazilian)
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/16291756/medium/2366972cc86287353708aff1ded3f3c1.jpg" alt="" width="16" height="16" /> lucasofchirst (Occitan, Portuguese, Portuguese, Brazilian)
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/16537713/medium/825f0bf1a14fc545a76891a52839d86e_default.png" alt="" width="16" height="16" /> marcin.kozinski (Polish)
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/13521465/medium/76cb9aa6b753ce900a70478bff7fcea0.png" alt="" width="16" height="16" /> mkljczkk (Polish)
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/12882812/medium/77744d8db46e9a3e09030e1a02b7a572.jpeg" alt="" width="16" height="16" /> mojosoeun (Korean)
@ -337,12 +337,14 @@ Costs involved in running and developing this web app:
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/16539171/medium/0ce95ef6b3b0566136191fbedc1563d0.png" alt="" width="16" height="16" /> SadmL_AI (Russian)
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/12381015/medium/35e3557fd61d85f9a5b84545d9e3feb4.png" alt="" width="16" height="16" /> shuuji3 (Japanese)
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/14565190/medium/79100599131b7776e9803e4b696915a3_default.png" alt="" width="16" height="16" /> Sky_NiniKo (French)
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/13143526/medium/2f15fa6d8e1703c7b82bb608b116a30a.png" alt="" width="16" height="16" /> Steffo99 (Italian)
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/16532441/medium/1a47e8d80c95636e02d2260f6e233ca5.png" alt="" width="16" height="16" /> Su5hicz (Czech)
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/16530049/medium/683f3581620c6b4a5c753b416ed695a7.jpeg" alt="" width="16" height="16" /> tferrermo (Spanish)
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/15752199/medium/7e9efd828c4691368d063b19d19eb894.png" alt="" width="16" height="16" /> tkbremnes (Norwegian Bokmal)
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/16527851/medium/649e5a9a8a8cc61ced670d89e9cca082.png" alt="" width="16" height="16" /> tux93 (German)
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/14427566/medium/ab733b5044c21867fc5a9d1b22cd2c03.png" alt="" width="16" height="16" /> Vac31. (Lithuanian)
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/16026914/medium/e3ca187f354a298ef0c9d02a0ed17be7.jpg" alt="" width="16" height="16" /> valtlai (Finnish)
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/16608515/medium/85506c21dce8df07843ca11908ee3951.jpeg" alt="" width="16" height="16" /> vasiriri (Polish)
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/16563757/medium/af4556c13862d1fd593b51084a159b75_default.png" alt="" width="16" height="16" /> voyagercy (Chinese Traditional)
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/15982109/medium/9c03062bdc1d3c6d384dbfead97c26ba.jpeg" alt="" width="16" height="16" /> xabi_itzultzaile (Basque)
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/16556017/medium/216e0f7a0c35b079920366939a3aaca7_default.png" alt="" width="16" height="16" /> xen4n (Ukrainian)

BIN
design/logo-bw-4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

12
design/logo-bw-4.svg Normal file
View file

@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2" clip-rule="evenodd" viewBox="0 0 64 64">
<path fill="none" d="M0 0h63.99v63.99H0z"/>
<clipPath id="a">
<path d="M0 0h63.99v63.99H0z"/>
</clipPath>
<g clip-path="url(#a)">
<path d="M0 0h64.25v63.99H0z"/>
<path fill="#fff" d="M37.77 11.47c14.64 3.75 19.04 16.56 15.9 31.3a12.55 12.55 0 0 1-6.36 8.7c-3.2 1.71-8.07 2.53-15.34.55l-9.64-2.4c-10.68-2.63-13.95-10.89-12.3-17.8 3.62-15.2 15.54-23.48 27.74-20.35Z"/>
<path d="M36.76 15.43c12.29 3.15 15.55 14.11 12.9 26.5-.94 4.43-4.93 9.36-16.66 6.13l-9.68-2.41c-7.85-1.93-10.53-7.8-9.32-12.88 3.02-12.64 12.61-19.94 22.76-17.34Z"/>
<path fill="#fff" d="M27.47 25c-1.46-.7-7.23 3.2-7.66 8.92-.18 2.39 4.55 3.23 5.07-.17.72-4.74 3.71-8.22 2.6-8.76Zm10.75 2c-2.09.32-.39 5.9-.6 10.72-.12 2.8 4.39 3.47 4.7 2.01 1.1-5.07-2.06-13.05-4.1-12.73Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 954 B

BIN
design/logo-wb-4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

12
design/logo-wb-4.svg Normal file
View file

@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2" clip-rule="evenodd" viewBox="0 0 64 64">
<path fill="none" d="M0 0h63.99v63.99H0z"/>
<clipPath id="a">
<path d="M0 0h63.99v63.99H0z"/>
</clipPath>
<g clip-path="url(#a)">
<path fill="#fff" d="M0 0h64.25v63.99H0z"/>
<path d="M37.77 11.47c14.64 3.75 19.04 16.56 15.9 31.3a12.55 12.55 0 0 1-6.36 8.7c-3.2 1.71-8.07 2.53-15.34.55l-9.64-2.4c-10.68-2.63-13.95-10.89-12.3-17.8 3.62-15.2 15.54-23.48 27.74-20.35Z"/>
<path fill="#fff" d="M36.76 15.43c12.29 3.15 15.55 14.11 12.9 26.5-.94 4.43-4.93 9.36-16.66 6.13l-9.68-2.41c-7.85-1.93-10.53-7.8-9.32-12.88 3.02-12.64 12.61-19.94 22.76-17.34Z"/>
<path d="M27.47 25c-1.46-.7-7.23 3.2-7.66 8.92-.18 2.39 4.55 3.23 5.07-.17.72-4.74 3.71-8.22 2.6-8.76Zm10.75 2c-2.09.32-.39 5.9-.6 10.72-.12 2.8 4.39 3.47 4.7 2.01 1.1-5.07-2.06-13.05-4.1-12.73Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 954 B

Binary file not shown.

56
package-lock.json generated
View file

@ -11,7 +11,7 @@
"@formatjs/intl-localematcher": "~0.5.4",
"@formatjs/intl-segmenter": "~11.5.7",
"@formkit/auto-animate": "~0.8.2",
"@github/text-expander-element": "~2.7.1",
"@github/text-expander-element": "~2.7.2",
"@iconify-icons/mingcute": "~1.2.9",
"@justinribeiro/lite-youtube": "~1.5.0",
"@lingui/detect-locale": "~4.11.4",
@ -32,7 +32,7 @@
"moize": "~6.1.6",
"p-retry": "~6.2.0",
"p-throttle": "~6.2.0",
"preact": "~10.24.0",
"preact": "~10.24.2",
"punycode": "~2.3.1",
"react-hotkeys-hook": "~4.5.1",
"react-intersection-observer": "~9.13.1",
@ -56,9 +56,9 @@
"babel-plugin-macros": "~3.1.0",
"postcss": "~8.4.47",
"postcss-dark-theme-class": "~1.3.0",
"postcss-preset-env": "~10.0.3",
"postcss-preset-env": "~10.0.6",
"twitter-text": "~3.1.0",
"vite": "~5.4.7",
"vite": "~5.4.8",
"vite-plugin-generate-file": "~0.2.0",
"vite-plugin-html-config": "~2.0.2",
"vite-plugin-pwa": "~0.20.5",
@ -2349,9 +2349,9 @@
}
},
"node_modules/@csstools/postcss-light-dark-function": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@csstools/postcss-light-dark-function/-/postcss-light-dark-function-2.0.2.tgz",
"integrity": "sha512-QAWWDJtJ7ywzhaMe09QwhjhuwB0XN04fW1MFwoEJMcYyiQub4a57mVFV+ngQEekUhsqe/EtKVCzyOx4q3xshag==",
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@csstools/postcss-light-dark-function/-/postcss-light-dark-function-2.0.4.tgz",
"integrity": "sha512-yHUt5DZ61Irvp72notmAl3Zt4Me50EWToWNocazyIFTVYFwwo/EucmV3hWi9zJehu3rOSvMclL7DzvRDfbak/A==",
"dev": true,
"funding": [
{
@ -3282,12 +3282,12 @@
"license": "MIT"
},
"node_modules/@github/text-expander-element": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/@github/text-expander-element/-/text-expander-element-2.7.1.tgz",
"integrity": "sha512-CWxfYxJRkeWVCUhJveproLs6pHsPrWtK8TsjL8ByYVcSCs8CJmNzF8b7ZawrUgfai0F2jb4aIdw2FoBTykj9XA==",
"version": "2.7.2",
"resolved": "https://registry.npmjs.org/@github/text-expander-element/-/text-expander-element-2.7.2.tgz",
"integrity": "sha512-eTIOUQKoBxe+e0yHKHQHoo4x61Erb7m0lhi2vMRHZS7TwI6OCGSj/3YydAr3obbQbZjevF9wPihLf1wADu3A9g==",
"dependencies": {
"@github/combobox-nav": "^2.0.2",
"dom-input-range": "^1.1.6"
"dom-input-range": "^1.2.0"
}
},
"node_modules/@ianvs/prettier-plugin-sort-imports": {
@ -5870,9 +5870,9 @@
}
},
"node_modules/dom-input-range": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/dom-input-range/-/dom-input-range-1.1.6.tgz",
"integrity": "sha512-4o/SkTpscD0n81BeErrrtmE58lG8vTks++92vk//ld0NmkQTb4AVJ2rexh2yor6rtBf5IMte26u+fF3EgCppPQ==",
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/dom-input-range/-/dom-input-range-1.2.0.tgz",
"integrity": "sha512-8HVA5Oy5Vt872S7IXsjjp6/5Hqsm5YZLhurxwwQXp80T9qVsj8/mEUH3sQlFujLLUoWfxiaThHHuJ3/q1MHVuA==",
"workspaces": [
"demos"
]
@ -8512,9 +8512,9 @@
}
},
"node_modules/postcss-custom-media": {
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-11.0.1.tgz",
"integrity": "sha512-vfBliYVgEEJUFXCRPQ7jYt1wlD322u+/5GT0tZqMVYFInkpDHfjhU3nk2quTRW4uFc/umOOqLlxvrEOZRvloMw==",
"version": "11.0.2",
"resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-11.0.2.tgz",
"integrity": "sha512-IDtxB1VgPayRLjNBMjuf827sn1j2m9EGnhIxpx2coVerbWJF+twt590+PKvdDc4K8QNFqFJh+W/SOiHpasVXsg==",
"dev": true,
"funding": [
{
@ -8945,9 +8945,9 @@
}
},
"node_modules/postcss-preset-env": {
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-10.0.3.tgz",
"integrity": "sha512-1nrZ4IeBXEEj53IMoRKE+k/Ub6nQb3gFjaxTeyUNG5zv3JQclFDY5GKKhAi3nsa1lnPMWgzQX+/1y6wUt2+I7Q==",
"version": "10.0.6",
"resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-10.0.6.tgz",
"integrity": "sha512-qixfM2wbvKJhUjJELLB8lV2UCsyrMdSXqiXHiNKMgAbNturstc80j/8MsthJeOpxYEekrCrFzcaoOJm8JRSdBg==",
"dev": true,
"funding": [
{
@ -8972,7 +8972,7 @@
"@csstools/postcss-ic-unit": "^4.0.0",
"@csstools/postcss-initial": "^2.0.0",
"@csstools/postcss-is-pseudo-class": "^5.0.0",
"@csstools/postcss-light-dark-function": "^2.0.2",
"@csstools/postcss-light-dark-function": "^2.0.4",
"@csstools/postcss-logical-float-and-clear": "^3.0.0",
"@csstools/postcss-logical-overflow": "^2.0.0",
"@csstools/postcss-logical-overscroll-behavior": "^2.0.0",
@ -9001,7 +9001,7 @@
"postcss-color-functional-notation": "^7.0.2",
"postcss-color-hex-alpha": "^10.0.0",
"postcss-color-rebeccapurple": "^10.0.0",
"postcss-custom-media": "^11.0.1",
"postcss-custom-media": "^11.0.2",
"postcss-custom-properties": "^14.0.1",
"postcss-custom-selectors": "^8.0.1",
"postcss-dir-pseudo-class": "^9.0.0",
@ -9110,9 +9110,9 @@
"license": "MIT"
},
"node_modules/preact": {
"version": "10.24.0",
"resolved": "https://registry.npmjs.org/preact/-/preact-10.24.0.tgz",
"integrity": "sha512-aK8Cf+jkfyuZ0ZZRG9FbYqwmEiGQ4y/PUO4SuTWoyWL244nZZh7bd5h2APd4rSNDYTBNghg1L+5iJN3Skxtbsw==",
"version": "10.24.2",
"resolved": "https://registry.npmjs.org/preact/-/preact-10.24.2.tgz",
"integrity": "sha512-1cSoF0aCC8uaARATfrlz4VCBqE8LwZwRfLgkxJOQwAlQt6ayTmi0D9OF7nXid1POI5SZidFuG9CnlXbDfLqY/Q==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/preact"
@ -10536,9 +10536,9 @@
}
},
"node_modules/vite": {
"version": "5.4.7",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.7.tgz",
"integrity": "sha512-5l2zxqMEPVENgvzTuBpHer2awaetimj2BGkhBPdnwKbPNOlHsODU+oiazEZzLK7KhAnOrO+XGYJYn4ZlUhDtDQ==",
"version": "5.4.8",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz",
"integrity": "sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==",
"dev": true,
"dependencies": {
"esbuild": "^0.21.3",

View file

@ -19,7 +19,7 @@
"@formatjs/intl-localematcher": "~0.5.4",
"@formatjs/intl-segmenter": "~11.5.7",
"@formkit/auto-animate": "~0.8.2",
"@github/text-expander-element": "~2.7.1",
"@github/text-expander-element": "~2.7.2",
"@iconify-icons/mingcute": "~1.2.9",
"@justinribeiro/lite-youtube": "~1.5.0",
"@lingui/detect-locale": "~4.11.4",
@ -40,7 +40,7 @@
"moize": "~6.1.6",
"p-retry": "~6.2.0",
"p-throttle": "~6.2.0",
"preact": "~10.24.0",
"preact": "~10.24.2",
"punycode": "~2.3.1",
"react-hotkeys-hook": "~4.5.1",
"react-intersection-observer": "~9.13.1",
@ -64,9 +64,9 @@
"babel-plugin-macros": "~3.1.0",
"postcss": "~8.4.47",
"postcss-dark-theme-class": "~1.3.0",
"postcss-preset-env": "~10.0.3",
"postcss-preset-env": "~10.0.6",
"twitter-text": "~3.1.0",
"vite": "~5.4.7",
"vite": "~5.4.8",
"vite-plugin-generate-file": "~0.2.0",
"vite-plugin-html-config": "~2.0.2",
"vite-plugin-pwa": "~0.20.5",

View file

@ -1722,7 +1722,7 @@ body:has(.media-modal-container + .status-deck) .media-post-link {
0 8px 16px -8px var(--drop-shadow-color);
}
}
@supports (animation-timeline: scroll()) {
@supports ((animation-timeline: scroll()) and (animation-range: 0% 100%)) {
.sheet header {
animation: header-border 1s linear both;
animation-timeline: --sheet-scroll;
@ -2363,7 +2363,7 @@ body > .szh-menu-container {
}
}
@supports (animation-timeline: scroll()) {
@supports ((animation-timeline: scroll()) and (animation-range: 0% 100%)) {
.header-account {
animation: peekaboo-header 1s linear both;
animation-timeline: scroll();

View file

@ -105,11 +105,13 @@ function AccountBlock({
}
}}
>
<Avatar
url={useAvatarStatic ? avatarStatic : avatar || avatarStatic}
size={avatarSize}
squircle={bot}
/>
<div class="avatar-container">
<Avatar
url={useAvatarStatic ? avatarStatic : avatar || avatarStatic}
size={avatarSize}
squircle={bot}
/>
</div>
<span class="account-block-content">
{!hideDisplayName && (
<>

View file

@ -217,13 +217,13 @@
background-image: none;
}
& + header .avatar + * {
& + header .avatar-container + * {
transition: opacity 0.3s ease-in-out;
opacity: 0 !important;
}
&,
& + header .avatar {
& + header .avatar-container {
transition: filter 0.3s ease-in-out;
filter: none !important;
}
@ -250,16 +250,21 @@
-8px 0 24px var(--header-color-3, --bg-color),
8px 0 24px var(--header-color-4, --bg-color);
animation: fade-in 0.3s both ease-in-out 0.1s;
}
.account-container header .avatar {
/* box-shadow: -8px 0 24px var(--header-color-3, --bg-color),
.avatar-container {
filter: drop-shadow(-2px 0 4px var(--header-color-3, --bg-color))
drop-shadow(2px 0 4px var(--header-color-4, --bg-color));
}
.avatar {
/* box-shadow: -8px 0 24px var(--header-color-3, --bg-color),
8px 0 24px var(--header-color-4, --bg-color); */
overflow: initial;
filter: drop-shadow(-2px 0 4px var(--header-color-3, --bg-color))
drop-shadow(2px 0 4px var(--header-color-4, --bg-color));
}
.account-container header .avatar:not(.has-alpha) img {
border-radius: inherit;
/* overflow: initial; */
&:not(.has-alpha) img {
border-radius: inherit;
}
}
}
.account-container main > *:first-child {
@ -585,7 +590,7 @@
margin-top: calc(-1 * var(--banner-overlap));
}
@supports (animation-timeline: scroll()) {
@supports ((animation-timeline: scroll()) and (animation-range: 0% 100%)) {
.header-banner:not(.header-is-avatar):not(:hover):not(:active) {
animation: bye-banner 1s linear both;
animation-timeline: view();
@ -754,12 +759,16 @@
letter-spacing: -0.5px;
mix-blend-mode: multiply;
gap: 12px;
}
.timeline-start .account-container header .account-block .avatar {
width: 112px !important;
height: 112px !important;
filter: drop-shadow(-8px 0 8px var(--header-color-3, --bg-color))
drop-shadow(8px 0 8px var(--header-color-4, --bg-color));
.avatar-container {
filter: drop-shadow(-8px 0 8px var(--header-color-3, --bg-color))
drop-shadow(8px 0 8px var(--header-color-4, --bg-color));
}
.avatar {
width: 112px !important;
height: 112px !important;
}
}
}

View file

@ -23,10 +23,12 @@ const ctx = canvas.getContext('2d', {
});
ctx.imageSmoothingEnabled = false;
const MISSING_IMAGE_PATH_REGEX = /missing\.png$/;
function Avatar({ url, size, alt = '', squircle, ...props }) {
size = SIZES[size] || size || SIZES.m;
const avatarRef = useRef();
const isMissing = /missing\.png$/.test(url);
const isMissing = MISSING_IMAGE_PATH_REGEX.test(url);
return (
<span
ref={avatarRef}
@ -48,6 +50,7 @@ function Avatar({ url, size, alt = '', squircle, ...props }) {
alt={alt}
loading="lazy"
decoding="async"
fetchPriority="low"
crossOrigin={
alphaCache[url] === undefined && !isMissing
? 'anonymous'

View file

@ -13,6 +13,7 @@ export default function CustomEmoji({ staticUrl, alt, url }) {
height="16"
loading="lazy"
decoding="async"
fetchPriority="low"
/>
</picture>
);

View file

@ -1,29 +1,45 @@
import { memo } from 'preact/compat';
import mem from '../utils/mem';
import CustomEmoji from './custom-emoji';
const shortcodesRegexp = mem((shortcodes) => {
return new RegExp(`:(${shortcodes.join('|')}):`, 'g');
});
function EmojiText({ text, emojis }) {
if (!text) return '';
if (!emojis?.length) return text;
if (text.indexOf(':') === -1) return text;
const regex = new RegExp(
`:(${emojis.map((e) => e.shortcode).join('|')}):`,
'g',
);
const elements = text.split(regex).map((word) => {
// const regex = new RegExp(
// `:(${emojis.map((e) => e.shortcode).join('|')}):`,
// 'g',
// );
const regex = shortcodesRegexp(emojis.map((e) => e.shortcode));
const elements = text.split(regex).map((word, i) => {
const emoji = emojis.find((e) => e.shortcode === word);
if (emoji) {
const { url, staticUrl } = emoji;
return <CustomEmoji staticUrl={staticUrl} alt={word} url={url} />;
return (
<CustomEmoji
staticUrl={staticUrl}
alt={word}
url={url}
key={word + '-' + i} // Handle >= 2 same shortcodes
/>
);
}
return word;
});
return elements;
}
export default memo(
EmojiText,
(oldProps, newProps) =>
oldProps.text === newProps.text &&
oldProps.emojis?.length === newProps.emojis?.length,
);
export default mem(EmojiText);
// export default memo(
// EmojiText,
// (oldProps, newProps) =>
// oldProps.text === newProps.text &&
// oldProps.emojis?.length === newProps.emojis?.length,
// );

View file

@ -21,6 +21,11 @@ const nameCollator = mem((locale) => {
}
});
const ACCT_REGEX = /([^@]+)(@.+)/i;
const SHORTCODES_REGEX = /(\:(\w|\+|\-)+\:)(?=|[\!\.\?]|$)/g;
const SPACES_REGEX = /\s+/g;
const NON_ALPHA_NUMERIC_REGEX = /[^a-z0-9@\.]/gi;
function NameText({
account,
instance,
@ -42,17 +47,17 @@ function NameText({
bot,
username,
} = account;
const [_, acct1, acct2] = acct.match(/([^@]+)(@.+)/i) || [, acct];
const [_, acct1, acct2] = acct.match(ACCT_REGEX) || [, acct];
if (!instance) instance = api().instance;
const trimmedUsername = username.toLowerCase().trim();
const trimmedDisplayName = (displayName || '').toLowerCase().trim();
const shortenedDisplayName = trimmedDisplayName
.replace(/(\:(\w|\+|\-)+\:)(?=|[\!\.\?]|$)/g, '') // Remove shortcodes, regex from https://regex101.com/r/iE9uV0/1
.replace(/\s+/g, ''); // E.g. "My name" === "myname"
.replace(SHORTCODES_REGEX, '') // Remove shortcodes, regex from https://regex101.com/r/iE9uV0/1
.replace(SPACES_REGEX, ''); // E.g. "My name" === "myname"
const shortenedAlphaNumericDisplayName = shortenedDisplayName.replace(
/[^a-z0-9@\.]/gi,
NON_ALPHA_NUMERIC_REGEX,
'',
); // Remove non-alphanumeric characters
@ -130,9 +135,11 @@ function NameText({
);
}
export default memo(NameText, (oldProps, newProps) => {
// Only care about account.id, the other props usually don't change
const { account } = oldProps;
const { account: newAccount } = newProps;
return account?.acct === newAccount?.acct;
});
export default mem(NameText);
// export default memo(NameText, (oldProps, newProps) => {
// // Only care about account.id, the other props usually don't change
// const { account } = oldProps;
// const { account: newAccount } = newProps;
// return account?.acct === newAccount?.acct;
// });

View file

@ -4,6 +4,7 @@ import { Fragment } from 'preact';
import { memo } from 'preact/compat';
import { api } from '../utils/api';
import { isFiltered } from '../utils/filters';
import shortenNumber from '../utils/shorten-number';
import states, { statusKey } from '../utils/states';
import { getCurrentAccountID } from '../utils/store-utils';
@ -447,9 +448,19 @@ function Notification({
console.debug('RENDER Notification', notification.id);
const sameCount =
notificationsCount > 0 && notificationsCount <= sampleAccounts?.length;
const expandAccounts = sameCount ? 'local' : 'remote';
const diffCount =
notificationsCount > 0 && notificationsCount > sampleAccounts?.length;
const expandAccounts = diffCount ? 'remote' : 'local';
// If there's a status and filter action is 'hide', then the notification is hidden
// TODO: Handle 'warn' action one day
if (!!status?.filtered) {
const isOwnPost = status?.account?.id === currentAccount;
const filterInfo = isFiltered(status.filtered, 'notifications');
if (!isSelf && !isOwnPost && filterInfo?.action === 'hide') {
return null;
}
}
return (
<div

View file

@ -1601,7 +1601,7 @@ body:has(#modal-container .carousel) .status .media img:hover {
margin-top: 8px;
padding: 8px;
@supports (animation-timeline: scroll()) {
@supports ((animation-timeline: scroll()) and (animation-range: 0% 100%)) {
animation: media-carousel-slide 1s linear both;
animation-timeline: --media-carousel;
}

View file

@ -20,6 +20,7 @@ import {
useCallback,
useContext,
useEffect,
useLayoutEffect,
useMemo,
useRef,
useState,
@ -124,11 +125,31 @@ function getPostText(status) {
);
}
const PostContent = memo(
const HTTP_REGEX = /^http/i;
const PostContent =
/*memo(*/
({ post, instance, previewMode }) => {
const { content, emojis, language, mentions, url } = post;
const divRef = useRef();
useLayoutEffect(() => {
if (!divRef.current) return;
const dom = enhanceContent(content, {
emojis,
returnDOM: true,
});
// Remove target="_blank" from links
for (const a of dom.querySelectorAll('a.u-url[target="_blank"]')) {
if (!HTTP_REGEX.test(a.innerText.trim())) {
a.removeAttribute('target');
}
}
divRef.current.replaceChildren(dom.cloneNode(true));
}, [content, emojis.length]);
return (
<div
ref={divRef}
lang={language}
dir="auto"
class="inner-content"
@ -138,28 +159,28 @@ const PostContent = memo(
previewMode,
statusURL: url,
})}
dangerouslySetInnerHTML={{
__html: enhanceContent(content, {
emojis,
postEnhanceDOM: (dom) => {
// Remove target="_blank" from links
dom.querySelectorAll('a.u-url[target="_blank"]').forEach((a) => {
if (!/http/i.test(a.innerText.trim())) {
a.removeAttribute('target');
}
});
},
}),
}}
// dangerouslySetInnerHTML={{
// __html: enhanceContent(content, {
// emojis,
// postEnhanceDOM: (dom) => {
// // Remove target="_blank" from links
// dom.querySelectorAll('a.u-url[target="_blank"]').forEach((a) => {
// if (!/http/i.test(a.innerText.trim())) {
// a.removeAttribute('target');
// }
// });
// },
// }),
// }}
/>
);
},
}; /*,
(oldProps, newProps) => {
const { post: oldPost } = oldProps;
const { post: newPost } = newProps;
return oldPost.content === newPost.content;
},
);
);*/
const SIZE_CLASS = {
s: 'small',
@ -189,6 +210,25 @@ const detectLang = pmem(async (text) => {
const readMoreText = msg`Read more →`;
// All this work just to make sure this only lazy-run once
// Because first run is slow due to intl-localematcher
const DIFFERENT_LANG_CHECK = {};
const checkDifferentLanguage = (
language,
contentTranslationHideLanguages = [],
) => {
if (!language) return false;
const targetLanguage = getTranslateTargetLanguage(true);
const different =
language !== targetLanguage &&
!localeMatch([language], [targetLanguage]) &&
!contentTranslationHideLanguages.find(
(l) => language === l || localeMatch([language], [l]),
);
DIFFERENT_LANG_CHECK[language + contentTranslationHideLanguages] = true;
return different;
};
function Status({
statusID,
status,
@ -513,9 +553,9 @@ function Status({
const isSizeLarge = size === 'l';
const [forceTranslate, setForceTranslate] = useState(_forceTranslate);
const targetLanguage = getTranslateTargetLanguage(true);
const contentTranslationHideLanguages =
snapStates.settings.contentTranslationHideLanguages || [];
// const targetLanguage = getTranslateTargetLanguage(true);
// const contentTranslationHideLanguages =
// snapStates.settings.contentTranslationHideLanguages || [];
const { contentTranslation, contentTranslationAutoInline } =
snapStates.settings;
if (!contentTranslation) enableTranslate = false;
@ -760,13 +800,37 @@ function Status({
} catch (e) {}
};
const differentLanguage =
!!language &&
language !== targetLanguage &&
!localeMatch([language], [targetLanguage]) &&
!contentTranslationHideLanguages.find(
(l) => language === l || localeMatch([language], [l]),
);
// const differentLanguage =
// !!language &&
// language !== targetLanguage &&
// !localeMatch([language], [targetLanguage]) &&
// !contentTranslationHideLanguages.find(
// (l) => language === l || localeMatch([language], [l]),
// );
const contentTranslationHideLanguages =
snapStates.settings.contentTranslationHideLanguages || [];
const [differentLanguage, setDifferentLanguage] = useState(
DIFFERENT_LANG_CHECK[language + contentTranslationHideLanguages]
? checkDifferentLanguage(language, contentTranslationHideLanguages)
: false,
);
useEffect(() => {
if (
!language ||
differentLanguage ||
DIFFERENT_LANG_CHECK[language + contentTranslationHideLanguages]
) {
return;
}
let timeout = setTimeout(() => {
const different = checkDifferentLanguage(
language,
contentTranslationHideLanguages,
);
if (different) setDifferentLanguage(different);
}, 1);
return () => clearTimeout(timeout);
}, [language, differentLanguage, contentTranslationHideLanguages]);
const reblogIterator = useRef();
const favouriteIterator = useRef();
@ -1228,6 +1292,9 @@ function Status({
</span>
</>
}
itemProps={{
className: 'danger',
}}
menuItemClassName="danger"
onClick={() => {
// const yes = confirm('Delete this post?');
@ -2721,6 +2788,8 @@ function Card({ card, selfReferential, selfAuthor, instance }) {
width={width}
height={height}
loading="lazy"
decoding="async"
fetchPriority="low"
alt={imageDescription || ''}
onError={(e) => {
try {
@ -3061,7 +3130,7 @@ function generateHTMLCode(post, instance, level = 0) {
: '');
const htmlCode = `
<blockquote lang="${language}" cite="${url}">
<blockquote lang="${language}" cite="${url}" data-source="fediverse">
${
spoilerText
? `

View file

@ -45,7 +45,7 @@
"code": "fa-IR",
"nativeName": "فارسی",
"name": "Persian",
"completion": 77
"completion": 78
},
{
"code": "fi-FI",
@ -99,13 +99,13 @@
"code": "lt-LT",
"nativeName": "lietuvių",
"name": "Lithuanian",
"completion": 80
"completion": 97
},
{
"code": "nb-NO",
"nativeName": "norsk bokmål",
"name": "Norwegian Bokmål",
"completion": 4
"completion": 31
},
{
"code": "nl-NL",

446
src/locales/ca-ES.po generated

File diff suppressed because it is too large Load diff

310
src/locales/eo-UY.po generated
View file

@ -8,7 +8,7 @@ msgstr ""
"Language: eo\n"
"Project-Id-Version: phanpy\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2024-09-22 19:29\n"
"PO-Revision-Date: 2024-10-06 13:22\n"
"Last-Translator: \n"
"Language-Team: Esperanto\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@ -18,40 +18,40 @@ msgstr ""
"X-Crowdin-File: /main/src/locales/en.po\n"
"X-Crowdin-File-ID: 18\n"
#: src/components/account-block.jsx:133
#: src/components/account-block.jsx:135
msgid "Locked"
msgstr "Ŝlosita"
#: src/components/account-block.jsx:139
#: src/components/account-block.jsx:141
msgid "Posts: {0}"
msgstr "Afiŝoj: {0}"
#: src/components/account-block.jsx:144
#: src/components/account-block.jsx:146
msgid "Last posted: {0}"
msgstr "Laste afiŝita: {0}"
#: src/components/account-block.jsx:159
#: src/components/account-block.jsx:161
#: src/components/account-info.jsx:634
msgid "Automated"
msgstr "Aŭtomatigita"
#: src/components/account-block.jsx:166
#: src/components/account-block.jsx:168
#: src/components/account-info.jsx:639
#: src/components/status.jsx:440
#: src/components/status.jsx:480
#: src/pages/catchup.jsx:1471
msgid "Group"
msgstr "Grupo"
#: src/components/account-block.jsx:176
#: src/components/account-block.jsx:178
msgid "Mutual"
msgstr "Reciproka"
#: src/components/account-block.jsx:180
#: src/components/account-block.jsx:182
#: src/components/account-info.jsx:1677
msgid "Requested"
msgstr "Petita"
#: src/components/account-block.jsx:184
#: src/components/account-block.jsx:186
#: src/components/account-info.jsx:416
#: src/components/account-info.jsx:742
#: src/components/account-info.jsx:756
@ -63,21 +63,21 @@ msgstr "Petita"
msgid "Following"
msgstr "Sekvatoj"
#: src/components/account-block.jsx:188
#: src/components/account-block.jsx:190
#: src/components/account-info.jsx:1059
msgid "Follows you"
msgstr "Sekvas vin"
#: src/components/account-block.jsx:196
#: src/components/account-block.jsx:198
msgid "{followersCount, plural, one {# follower} other {# followers}}"
msgstr "{followersCount, plural, one {# sekvanto} other {# sekvantoj}}"
#: src/components/account-block.jsx:205
#: src/components/account-block.jsx:207
#: src/components/account-info.jsx:680
msgid "Verified"
msgstr "Kontrolita"
#: src/components/account-block.jsx:220
#: src/components/account-block.jsx:222
#: src/components/account-info.jsx:777
msgid "Joined <0>{0}</0>"
msgstr "Aliĝis <0>{0}</0>"
@ -113,11 +113,11 @@ msgstr "Afiŝoj"
#: src/components/compose.jsx:2463
#: src/components/media-alt-modal.jsx:45
#: src/components/media-modal.jsx:283
#: src/components/status.jsx:1636
#: src/components/status.jsx:1653
#: src/components/status.jsx:1777
#: src/components/status.jsx:2375
#: src/components/status.jsx:2378
#: src/components/status.jsx:1700
#: src/components/status.jsx:1717
#: src/components/status.jsx:1841
#: src/components/status.jsx:2439
#: src/components/status.jsx:2442
#: src/pages/account-statuses.jsx:528
#: src/pages/accounts.jsx:109
#: src/pages/hashtag.jsx:199
@ -186,7 +186,7 @@ msgid "Original"
msgstr "Originala"
#: src/components/account-info.jsx:859
#: src/components/status.jsx:2166
#: src/components/status.jsx:2230
#: src/pages/catchup.jsx:71
#: src/pages/catchup.jsx:1445
#: src/pages/catchup.jsx:2056
@ -279,33 +279,33 @@ msgstr "Ŝalti diskonigojn"
#: src/components/account-info.jsx:1260
#: src/components/account-info.jsx:1861
msgid "Add/Remove from Lists"
msgstr "Aldoni / Forigi el listoj"
msgstr "Aldoni/Forigi el Listoj"
#: src/components/account-info.jsx:1299
#: src/components/status.jsx:1079
#: src/components/status.jsx:1143
msgid "Link copied"
msgstr "Ligilo kopiita"
#: src/components/account-info.jsx:1302
#: src/components/status.jsx:1082
#: src/components/status.jsx:1146
msgid "Unable to copy link"
msgstr "Ne eblas kopii la ligilon"
#: src/components/account-info.jsx:1308
#: src/components/shortcuts-settings.jsx:1056
#: src/components/status.jsx:1088
#: src/components/status.jsx:3150
#: src/components/status.jsx:1152
#: src/components/status.jsx:3214
msgid "Copy"
msgstr "Kopii"
#: src/components/account-info.jsx:1323
#: src/components/shortcuts-settings.jsx:1074
#: src/components/status.jsx:1104
#: src/components/status.jsx:1168
msgid "Sharing doesn't seem to work."
msgstr "Kunhavigo ŝajnas ne funkcii."
#: src/components/account-info.jsx:1329
#: src/components/status.jsx:1110
#: src/components/status.jsx:1174
msgid "Share…"
msgstr "Diskonigi…"
@ -422,9 +422,9 @@ msgstr "Sekvi"
#: src/components/shortcuts-settings.jsx:227
#: src/components/shortcuts-settings.jsx:580
#: src/components/shortcuts-settings.jsx:780
#: src/components/status.jsx:2875
#: src/components/status.jsx:3114
#: src/components/status.jsx:3612
#: src/components/status.jsx:2939
#: src/components/status.jsx:3178
#: src/components/status.jsx:3676
#: src/pages/accounts.jsx:36
#: src/pages/catchup.jsx:1581
#: src/pages/filters.jsx:224
@ -610,7 +610,7 @@ msgid "Attachment #{i} failed"
msgstr "Eraro aldonante #{i}"
#: src/components/compose.jsx:1118
#: src/components/status.jsx:1962
#: src/components/status.jsx:2026
#: src/components/timeline.jsx:984
msgid "Content warning"
msgstr "Enhavaverto"
@ -620,7 +620,7 @@ msgid "Content warning or sensitive media"
msgstr "Enhavaverto aŭ sentema plurmedio"
#: src/components/compose.jsx:1170
#: src/components/status.jsx:92
#: src/components/status.jsx:93
#: src/pages/settings.jsx:297
msgid "Public"
msgstr "Publika"
@ -628,25 +628,25 @@ msgstr "Publika"
#: src/components/compose.jsx:1175
#: src/components/nav-menu.jsx:386
#: src/components/shortcuts-settings.jsx:162
#: src/components/status.jsx:93
#: src/components/status.jsx:94
msgid "Local"
msgstr "Loka"
#: src/components/compose.jsx:1179
#: src/components/status.jsx:94
#: src/components/status.jsx:95
#: src/pages/settings.jsx:300
msgid "Unlisted"
msgstr "Nelistigita"
#: src/components/compose.jsx:1182
#: src/components/status.jsx:95
#: src/components/status.jsx:96
#: src/pages/settings.jsx:303
msgid "Followers only"
msgstr "Nur sekvantoj"
#: src/components/compose.jsx:1185
#: src/components/status.jsx:96
#: src/components/status.jsx:1840
#: src/components/status.jsx:97
#: src/components/status.jsx:1904
msgid "Private mention"
msgstr "Privata mencio"
@ -676,10 +676,10 @@ msgstr "Aldoni propran emoĝion"
#: src/components/compose.jsx:1479
#: src/components/keyboard-shortcuts-help.jsx:143
#: src/components/status.jsx:831
#: src/components/status.jsx:1616
#: src/components/status.jsx:1617
#: src/components/status.jsx:2271
#: src/components/status.jsx:895
#: src/components/status.jsx:1680
#: src/components/status.jsx:1681
#: src/components/status.jsx:2335
msgid "Reply"
msgstr "Respondi"
@ -894,7 +894,7 @@ msgstr "Eraro dum forigo de malneto! Bonvolu provi denove."
#: src/components/drafts.jsx:127
#: src/components/list-add-edit.jsx:183
#: src/components/status.jsx:1251
#: src/components/status.jsx:1315
#: src/pages/filters.jsx:587
msgid "Delete…"
msgstr "Forigi…"
@ -1094,10 +1094,10 @@ msgid "<0>l</0> or <1>f</1>"
msgstr "<0>l</0> aŭ <1>f</1>"
#: src/components/keyboard-shortcuts-help.jsx:164
#: src/components/status.jsx:839
#: src/components/status.jsx:2297
#: src/components/status.jsx:2329
#: src/components/status.jsx:2330
#: src/components/status.jsx:903
#: src/components/status.jsx:2361
#: src/components/status.jsx:2393
#: src/components/status.jsx:2394
msgid "Boost"
msgstr "Diskonigi"
@ -1106,9 +1106,9 @@ msgid "<0>Shift</0> + <1>b</1>"
msgstr "<0>Ŝovo</0> + <1>b</1>"
#: src/components/keyboard-shortcuts-help.jsx:172
#: src/components/status.jsx:924
#: src/components/status.jsx:2354
#: src/components/status.jsx:2355
#: src/components/status.jsx:988
#: src/components/status.jsx:2418
#: src/components/status.jsx:2419
msgid "Bookmark"
msgstr "Legosigni"
@ -1167,15 +1167,15 @@ msgid "Media description"
msgstr "Priskribo de plurmedio"
#: src/components/media-alt-modal.jsx:57
#: src/components/status.jsx:968
#: src/components/status.jsx:995
#: src/components/status.jsx:1032
#: src/components/status.jsx:1059
#: src/components/translation-block.jsx:195
msgid "Translate"
msgstr "Traduki"
#: src/components/media-alt-modal.jsx:68
#: src/components/status.jsx:982
#: src/components/status.jsx:1009
#: src/components/status.jsx:1046
#: src/components/status.jsx:1073
msgid "Speak"
msgstr "Paroli"
@ -1212,9 +1212,9 @@ msgid "Filtered: {filterTitleStr}"
msgstr "Filtrila: {filterTitleStr}"
#: src/components/media-post.jsx:133
#: src/components/status.jsx:3442
#: src/components/status.jsx:3538
#: src/components/status.jsx:3616
#: src/components/status.jsx:3506
#: src/components/status.jsx:3602
#: src/components/status.jsx:3680
#: src/components/timeline.jsx:973
#: src/pages/catchup.jsx:75
#: src/pages/catchup.jsx:1876
@ -1411,7 +1411,7 @@ msgstr "{account} petis sekvi vin."
#: src/components/notification.jsx:150
msgid "{count, plural, =1 {{postsCount, plural, =1 {{postType, select, reply {{account} liked your reply.} other {{account} liked your post.}}} other {{account} liked {postsCount} of your posts.}}} other {{postType, select, reply {<0><1>{0}</1> people</0> liked your reply.} other {<2><3>{1}</3> people</2> liked your post.}}}}"
msgstr "{count, plural, =1 {{postsCount, plural, =1 {{postType, select, reply {{account} stelumis vian respondon.} other {{account} stelumis vian afiŝon.}}} other {{account} stelumis {postsCount} de viaj afiŝoj.}}} other {{postType, select, reply {<0><1>{0}</1> homoj</0> stelumis vian respondon.} other {<2><3>{1}</3> homoj</2> stelumis vian afiŝon.}}}}"
msgstr "{count, plural, =1 {{postsCount, plural, =1 {{postType, select, reply {{account} ŝatis vian respondon.} other {{account} ŝatis vian afiŝon.}}} other {{account} ŝatis {postsCount} de viaj afiŝoj.}}} other {{postType, select, reply {<0><1>{0}</1> homoj</0> ŝatis vian respondon.} other {<2><3>{1}</3> homoj</2> ŝatis vian afiŝon.}}}}"
#: src/components/notification.jsx:192
msgid "A poll you have voted in or created has ended."
@ -1431,7 +1431,7 @@ msgstr "Afiŝo, kun kiu vi interagis, estis redaktita."
#: src/components/notification.jsx:203
msgid "{count, plural, =1 {{postsCount, plural, =1 {{postType, select, reply {{account} boosted & liked your reply.} other {{account} boosted & liked your post.}}} other {{account} boosted & liked {postsCount} of your posts.}}} other {{postType, select, reply {<0><1>{0}</1> people</0> boosted & liked your reply.} other {<2><3>{1}</3> people</2> boosted & liked your post.}}}}"
msgstr "{count, plural, =1 {{postsCount, plural, =1 {{postType, select, reply {{account} diskonigis kaj stelumis vian respondon.} other {{account} diskonigis kaj stelumis vian afiŝon.}}} other {{account} diskonigis kaj stelumis {postsCount} de viaj afiŝoj.}}} other {{postType, select, reply {<0><1>{0}</1> homoj</0> diskonigis kaj stelumis vian respondon.} other {<2><3>{1}</3> homoj</2> diskonigis kaj stelumis vian afiŝon.}}}}"
msgstr "{count, plural, =1 {{postsCount, plural, =1 {{postType, select, reply {{account} diskonigis kaj ŝatis vian respondon.} other {{account} diskonigis kaj ŝatis vian afiŝon.}}} other {{account} diskonigis kaj ŝatis {postsCount} de viaj afiŝoj.}}} other {{postType, select, reply {<0><1>{0}</1> homoj</0> diskonigis kaj ŝatis vian respondon.} other {<2><3>{1}</3> homoj</2> diskonigis kaj ŝatis vian afiŝon.}}}}"
#: src/components/notification.jsx:245
msgid "{account} signed up."
@ -1494,8 +1494,8 @@ msgid "[Unknown notification type: {type}]"
msgstr "[Nekonata tipo de sciigo: {type}]"
#: src/components/notification.jsx:433
#: src/components/status.jsx:938
#: src/components/status.jsx:948
#: src/components/status.jsx:1002
#: src/components/status.jsx:1012
msgid "Boosted/Liked by…"
msgstr "Diskonigita/Ŝatita de…"
@ -1517,7 +1517,7 @@ msgid "Learn more <0/>"
msgstr "Lerni pli <0/>"
#: src/components/notification.jsx:745
#: src/components/status.jsx:190
#: src/components/status.jsx:211
msgid "Read more →"
msgstr "Legi pli →"
@ -1810,7 +1810,7 @@ msgid "Move down"
msgstr "Moviĝi malsupren"
#: src/components/shortcuts-settings.jsx:376
#: src/components/status.jsx:1216
#: src/components/status.jsx:1280
#: src/pages/list.jsx:170
msgid "Edit"
msgstr "Redakti"
@ -2008,297 +2008,297 @@ msgstr "Ne estas plu ŝparvojoj por importi"
msgid "Import/export settings from/to instance server (Very experimental)"
msgstr "Importi/eksporti agordojn de/al nodservilo (Tre eksperimenta)"
#: src/components/status.jsx:464
#: src/components/status.jsx:504
msgid "<0/> <1>boosted</1>"
msgstr "<0/> <1>diskonigita</1>"
#: src/components/status.jsx:563
#: src/components/status.jsx:603
msgid "Sorry, your current logged-in instance can't interact with this post from another instance."
msgstr "Pardonu, la nodo en kiu vi estas ensalutinta ne permesas vin interagi kun ĉi tiu afiŝo de alia nodo."
#: src/components/status.jsx:716
#: src/components/status.jsx:756
msgid "Unliked @{0}'s post"
msgstr "Ne plu ŝatis la afiŝon de {0}"
#: src/components/status.jsx:717
#: src/components/status.jsx:757
msgid "Liked @{0}'s post"
msgstr "Ŝatis la afiŝon de {0}"
#: src/components/status.jsx:756
#: src/components/status.jsx:796
msgid "Unbookmarked @{0}'s post"
msgstr "Ne plu legosignis la afiŝon de @{0}"
#: src/components/status.jsx:757
#: src/components/status.jsx:797
msgid "Bookmarked @{0}'s post"
msgstr "Legosignis la afiŝon de @{0}"
#: src/components/status.jsx:839
#: src/components/status.jsx:901
#: src/components/status.jsx:2297
#: src/components/status.jsx:2329
#: src/components/status.jsx:903
#: src/components/status.jsx:965
#: src/components/status.jsx:2361
#: src/components/status.jsx:2393
msgid "Unboost"
msgstr "Ne plu diskonigi"
#: src/components/status.jsx:855
#: src/components/status.jsx:2312
#: src/components/status.jsx:919
#: src/components/status.jsx:2376
msgid "Quote"
msgstr "Citaĵo"
#: src/components/status.jsx:863
#: src/components/status.jsx:2321
#: src/components/status.jsx:927
#: src/components/status.jsx:2385
msgid "Some media have no descriptions."
msgstr "Iuj plurmedioj ne havas priskribojn."
#: src/components/status.jsx:870
#: src/components/status.jsx:934
msgid "Old post (<0>{0}</0>)"
msgstr "Malnova afiŝo (<0>{0}</0>)"
#: src/components/status.jsx:889
#: src/components/status.jsx:1341
#: src/components/status.jsx:953
#: src/components/status.jsx:1405
msgid "Unboosted @{0}'s post"
msgstr "Ne plu diskonigita la afiŝon de @{0}"
#: src/components/status.jsx:890
#: src/components/status.jsx:1342
#: src/components/status.jsx:954
#: src/components/status.jsx:1406
msgid "Boosted @{0}'s post"
msgstr "Diskonigita la afiŝon de @{0}"
#: src/components/status.jsx:902
#: src/components/status.jsx:966
msgid "Boost…"
msgstr "Diskonigi…"
#: src/components/status.jsx:914
#: src/components/status.jsx:1626
#: src/components/status.jsx:2342
#: src/components/status.jsx:978
#: src/components/status.jsx:1690
#: src/components/status.jsx:2406
msgid "Unlike"
msgstr "Ne plu ŝati"
#: src/components/status.jsx:915
#: src/components/status.jsx:1626
#: src/components/status.jsx:1627
#: src/components/status.jsx:2342
#: src/components/status.jsx:2343
#: src/components/status.jsx:979
#: src/components/status.jsx:1690
#: src/components/status.jsx:1691
#: src/components/status.jsx:2406
#: src/components/status.jsx:2407
msgid "Like"
msgstr "Ŝatata"
#: src/components/status.jsx:924
#: src/components/status.jsx:2354
#: src/components/status.jsx:988
#: src/components/status.jsx:2418
msgid "Unbookmark"
msgstr "Ne plu legosignis"
#: src/components/status.jsx:1032
#: src/components/status.jsx:1096
msgid "View post by <0>@{0}</0>"
msgstr "Vidi afiŝon de <0>@{0}</0>"
#: src/components/status.jsx:1053
#: src/components/status.jsx:1117
msgid "Show Edit History"
msgstr "Montri redaktan historion"
#: src/components/status.jsx:1056
#: src/components/status.jsx:1120
msgid "Edited: {editedDateText}"
msgstr "Redaktita: {editedDateText}"
#: src/components/status.jsx:1123
#: src/components/status.jsx:3119
#: src/components/status.jsx:1187
#: src/components/status.jsx:3183
msgid "Embed post"
msgstr "Enkorpigi afiŝon"
#: src/components/status.jsx:1137
#: src/components/status.jsx:1201
msgid "Conversation unmuted"
msgstr "Konversacion nesilentigita"
#: src/components/status.jsx:1137
#: src/components/status.jsx:1201
msgid "Conversation muted"
msgstr "Konversacion silentigita"
#: src/components/status.jsx:1143
#: src/components/status.jsx:1207
msgid "Unable to unmute conversation"
msgstr "Ne eblas nesilentigi konversacion"
#: src/components/status.jsx:1144
#: src/components/status.jsx:1208
msgid "Unable to mute conversation"
msgstr "Ne eblas silentigi konversacion"
#: src/components/status.jsx:1153
#: src/components/status.jsx:1217
msgid "Unmute conversation"
msgstr "Nesilentigi konversacion"
#: src/components/status.jsx:1160
#: src/components/status.jsx:1224
msgid "Mute conversation"
msgstr "Silentigi konversacion"
#: src/components/status.jsx:1176
#: src/components/status.jsx:1240
msgid "Post unpinned from profile"
msgstr "Afiŝo depinglinta de profilo"
#: src/components/status.jsx:1177
#: src/components/status.jsx:1241
msgid "Post pinned to profile"
msgstr "Afiŝo alpinglita al profilo"
#: src/components/status.jsx:1182
#: src/components/status.jsx:1246
msgid "Unable to unpin post"
msgstr "Ne eblas depingli afiŝon"
#: src/components/status.jsx:1182
#: src/components/status.jsx:1246
msgid "Unable to pin post"
msgstr "Ne eblas alpingli afiŝon"
#: src/components/status.jsx:1191
#: src/components/status.jsx:1255
msgid "Unpin from profile"
msgstr "Depingli de profilo"
#: src/components/status.jsx:1198
#: src/components/status.jsx:1262
msgid "Pin to profile"
msgstr "Alpingli al la profilo"
#: src/components/status.jsx:1227
#: src/components/status.jsx:1291
msgid "Delete this post?"
msgstr "Ĉu forigi ĉi tiun afiŝon?"
#: src/components/status.jsx:1240
#: src/components/status.jsx:1304
msgid "Post deleted"
msgstr "Afiŝo forigita"
#: src/components/status.jsx:1243
#: src/components/status.jsx:1307
msgid "Unable to delete post"
msgstr "Ne eblas forigi afiŝon"
#: src/components/status.jsx:1271
#: src/components/status.jsx:1335
msgid "Report post…"
msgstr "Raporti afiŝon…"
#: src/components/status.jsx:1627
#: src/components/status.jsx:1663
#: src/components/status.jsx:2343
#: src/components/status.jsx:1691
#: src/components/status.jsx:1727
#: src/components/status.jsx:2407
msgid "Liked"
msgstr "Ŝatita"
#: src/components/status.jsx:1660
#: src/components/status.jsx:2330
#: src/components/status.jsx:1724
#: src/components/status.jsx:2394
msgid "Boosted"
msgstr "Diskonigita"
#: src/components/status.jsx:1670
#: src/components/status.jsx:2355
#: src/components/status.jsx:1734
#: src/components/status.jsx:2419
msgid "Bookmarked"
msgstr "Legosignita"
#: src/components/status.jsx:1674
#: src/components/status.jsx:1738
msgid "Pinned"
msgstr "Alpinglita"
#: src/components/status.jsx:1719
#: src/components/status.jsx:2174
#: src/components/status.jsx:1783
#: src/components/status.jsx:2238
msgid "Deleted"
msgstr "Forigita"
#: src/components/status.jsx:1760
#: src/components/status.jsx:1824
msgid "{repliesCount, plural, one {# reply} other {# replies}}"
msgstr "{repliesCount, plural, one {# respondo} other {# respondoj}}"
#: src/components/status.jsx:1849
#: src/components/status.jsx:1913
msgid "Thread{0}"
msgstr "Fadeno{0}"
#: src/components/status.jsx:1925
#: src/components/status.jsx:1987
#: src/components/status.jsx:2072
#: src/components/status.jsx:1989
#: src/components/status.jsx:2051
#: src/components/status.jsx:2136
msgid "Show less"
msgstr "Montri malpli"
#: src/components/status.jsx:1925
#: src/components/status.jsx:1987
#: src/components/status.jsx:1989
#: src/components/status.jsx:2051
msgid "Show content"
msgstr "Montri enhavon"
#: src/components/status.jsx:2072
#: src/components/status.jsx:2136
msgid "Show media"
msgstr "Montri plurmedion"
#: src/components/status.jsx:2195
#: src/components/status.jsx:2259
msgid "Edited"
msgstr "Redaktita"
#: src/components/status.jsx:2272
#: src/components/status.jsx:2336
msgid "Comments"
msgstr "Komentoj"
#. More from [Author]
#: src/components/status.jsx:2581
#: src/components/status.jsx:2645
msgid "More from <0/>"
msgstr "Pli de <0/>"
#: src/components/status.jsx:2880
#: src/components/status.jsx:2944
msgid "Edit History"
msgstr "Redaktan historion"
#: src/components/status.jsx:2884
#: src/components/status.jsx:2948
msgid "Failed to load history"
msgstr "Malsukcesis ŝargi historion"
#: src/components/status.jsx:2889
#: src/components/status.jsx:2953
msgid "Loading…"
msgstr "Ŝargante…"
#: src/components/status.jsx:3124
#: src/components/status.jsx:3188
msgid "HTML Code"
msgstr "HTML-kodo"
#: src/components/status.jsx:3141
#: src/components/status.jsx:3205
msgid "HTML code copied"
msgstr "HTML-kodo kopiita"
#: src/components/status.jsx:3144
#: src/components/status.jsx:3208
msgid "Unable to copy HTML code"
msgstr "Ne eblas kopii HTML-kodon"
#: src/components/status.jsx:3156
#: src/components/status.jsx:3220
msgid "Media attachments:"
msgstr "Plurmediaj aldonaĵoj:"
#: src/components/status.jsx:3178
#: src/components/status.jsx:3242
msgid "Account Emojis:"
msgstr "Emoĝioj de la konto:"
#: src/components/status.jsx:3209
#: src/components/status.jsx:3254
#: src/components/status.jsx:3273
#: src/components/status.jsx:3318
msgid "static URL"
msgstr "statika URL"
#: src/components/status.jsx:3223
#: src/components/status.jsx:3287
msgid "Emojis:"
msgstr "Emoĝioj:"
#: src/components/status.jsx:3268
#: src/components/status.jsx:3332
msgid "Notes:"
msgstr "Notoj:"
#: src/components/status.jsx:3272
#: src/components/status.jsx:3336
msgid "This is static, unstyled and scriptless. You may need to apply your own styles and edit as needed."
msgstr "Ĉi tio estas senmova, ne stilita kaj senskriba. Vi eble bezonos apliki viajn proprajn stilojn kaj redakti laŭbezone."
#: src/components/status.jsx:3278
#: src/components/status.jsx:3342
msgid "Polls are not interactive, becomes a list with vote counts."
msgstr "Balotenketoj ne estas interagaj kaj estos konvertitaj al listo kun balotkalkuloj."
#: src/components/status.jsx:3283
#: src/components/status.jsx:3347
msgid "Media attachments can be images, videos, audios or any file types."
msgstr "Plurmediaj aldonaĵoj povas esti bildoj, filmetoj, sonaĵoj aŭ ajnaj dosiertipoj."
#: src/components/status.jsx:3289
#: src/components/status.jsx:3353
msgid "Post could be edited or deleted later."
msgstr "Afiŝo povus esti redaktita aŭ forigita poste."
#: src/components/status.jsx:3295
#: src/components/status.jsx:3359
msgid "Preview"
msgstr "Antaŭrigardo"
#: src/components/status.jsx:3304
#: src/components/status.jsx:3368
msgid "Note: This preview is lightly styled."
msgstr "Noto: Ĉi tiu antaŭmontro estas malpeze stilita."
#. [Name] [Visibility icon] boosted
#: src/components/status.jsx:3546
#: src/components/status.jsx:3610
msgid "<0/> <1/> boosted"
msgstr "<0/> <1/> diskonigita"
@ -2705,7 +2705,7 @@ msgstr "Nenio"
#: src/pages/catchup.jsx:1503
msgid "Show all authors"
msgstr "Montru ĉiujn aŭtorojn"
msgstr "Montri ĉiujn aŭtorojn"
#: src/pages/catchup.jsx:1554
msgid "You don't have to read everything."
@ -2931,7 +2931,7 @@ msgstr "Neniu ankoraŭ afiŝis ion kun ĉi tiu kradvorto."
#: src/pages/hashtag.jsx:182
msgid "Unable to load posts with this tag"
msgstr "Ne eblas ŝargi afiŝojn kun tiu ĉi kradvorto"
msgstr "Ne eblas ŝarĝi afiŝojn kun ĉi tiu kradvorto"
#: src/pages/hashtag.jsx:208
msgid "Unfollow #{hashtag}?"

420
src/locales/es-ES.po generated

File diff suppressed because it is too large Load diff

454
src/locales/eu-ES.po generated

File diff suppressed because it is too large Load diff

802
src/locales/fa-IR.po generated

File diff suppressed because it is too large Load diff

456
src/locales/it-IT.po generated

File diff suppressed because it is too large Load diff

780
src/locales/lt-LT.po generated

File diff suppressed because it is too large Load diff

926
src/locales/nb-NO.po generated

File diff suppressed because it is too large Load diff

454
src/locales/pl-PL.po generated

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,7 @@
#settings-container {
background-color: var(--bg-faded-color);
/* Prevent immediate text size change affecting max width */
max-width: calc(40 * var(--current-text-size) - 50px - 16px);
}
#settings-container main h3 {

View file

@ -63,7 +63,14 @@ function Settings({ onClose }) {
// }, []);
return (
<div id="settings-container" class="sheet" tabIndex="-1">
<div
id="settings-container"
class="sheet"
tabIndex="-1"
style={{
'--current-text-size': `${currentTextSize}px`,
}}
>
{!!onClose && (
<button type="button" class="sheet-close" onClick={onClose}>
<Icon icon="x" alt={t`Close`} />

View file

@ -44,6 +44,8 @@ import useTitle from '../utils/useTitle';
import getInstanceStatusURL from './../utils/get-instance-status-url';
const { PHANPY_DEFAULT_INSTANCE: DEFAULT_INSTANCE } = import.meta.env;
const LIMIT = 40;
const SUBCOMMENTS_OPEN_ALL_LIMIT = 10;
const MAX_WEIGHT = 5;
@ -788,7 +790,14 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) {
not possible.
</Trans>
</p>
<Link to="/login" class="button">
<Link
to={
DEFAULT_INSTANCE
? `/login?instance=${DEFAULT_INSTANCE}&submit=1`
: '/login'
}
class="button"
>
<Trans>Log in</Trans>
</Link>
</div>

View file

@ -8,7 +8,7 @@ function emojifyText(text, emojis = []) {
const { shortcode, staticUrl, url } = emoji;
text = text.replace(
new RegExp(`:${shortcode}:`, 'g'),
`<picture><source srcset="${staticUrl}" media="(prefers-reduced-motion: reduce)"></source><img class="shortcode-emoji emoji" src="${url}" alt=":${shortcode}:" width="16" height="16" loading="lazy" decoding="async" /></picture>`,
`<picture><source srcset="${staticUrl}" media="(prefers-reduced-motion: reduce)"></source><img class="shortcode-emoji emoji" src="${url}" alt=":${shortcode}:" width="16" height="16" loading="lazy" decoding="async" fetchPriority="low" /></picture>`,
);
});
// console.log(text, emojis);

View file

@ -4,53 +4,85 @@ import mem from './mem';
const fauxDiv = document.createElement('div');
const whitelistLinkClasses = ['u-url', 'mention', 'hashtag'];
const HTML_CHARS_REGEX = /[&<>]/g;
function escapeHTML(html) {
return html.replace(
HTML_CHARS_REGEX,
(c) =>
({
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
}[c]),
);
}
const LINK_REGEX = /<a/i;
const HTTP_LINK_REGEX = /^https?:\/\//i;
const MENTION_REGEX = /^@[^@]+(@[^@]+)?$/;
const HASHTAG_REGEX = /^#[^#]+$/;
const CODE_BLOCK_REGEX = /^```[^]+```$/;
const CODE_BLOCK_START_REGEX = /^```/;
const CODE_BLOCK_END_REGEX = /```$/;
const INLINE_CODE_REGEX = /`[^`]+`/;
const TWITTER_DOMAIN_REGEX = /(twitter|x)\.com/i;
const TWITTER_MENTION_REGEX = /@[a-zA-Z0-9_]+@(twitter|x)\.com/;
const TWITTER_MENTION_CAPTURE_REGEX = /(@([a-zA-Z0-9_]+)@(twitter|x)\.com)/;
function createDOM(html, isDocumentFragment) {
const tpl = document.createElement('template');
tpl.innerHTML = html;
return isDocumentFragment ? tpl.content : tpl;
}
function _enhanceContent(content, opts = {}) {
const { emojis, postEnhanceDOM = () => {} } = opts;
const { emojis, returnDOM, postEnhanceDOM = () => {} } = opts;
let enhancedContent = content;
const dom = document.createElement('div');
dom.innerHTML = enhancedContent;
const hasLink = /<a/i.test(enhancedContent);
// const dom = document.createElement('div');
// dom.innerHTML = enhancedContent;
const dom = createDOM(enhancedContent, returnDOM);
const hasLink = LINK_REGEX.test(enhancedContent);
const hasCodeBlock = enhancedContent.includes('```');
if (hasLink) {
// Add target="_blank" to all links with no target="_blank"
// E.g. `note` in `account`
const noTargetBlankLinks = dom.querySelectorAll('a:not([target="_blank"])');
noTargetBlankLinks.forEach((link) => {
for (const link of noTargetBlankLinks) {
link.setAttribute('target', '_blank');
});
}
// Remove all classes except `u-url`, `mention`, `hashtag`
const links = dom.querySelectorAll('a[class]');
links.forEach((link) => {
link.classList.forEach((c) => {
for (const link of links) {
for (const c of link.classList) {
if (!whitelistLinkClasses.includes(c)) {
link.classList.remove(c);
}
});
});
}
}
}
// Add 'has-url-text' to all links that contains a url
if (hasLink) {
const links = dom.querySelectorAll('a[href]');
links.forEach((link) => {
if (/^https?:\/\//i.test(link.textContent.trim())) {
for (const link of links) {
if (HTTP_LINK_REGEX.test(link.textContent.trim())) {
link.classList.add('has-url-text');
shortenLink(link);
}
});
}
}
// Spanify un-spanned mentions
if (hasLink) {
const links = dom.querySelectorAll('a[href]');
const usernames = [];
links.forEach((link) => {
for (const link of links) {
const text = link.innerText.trim();
const hasChildren = link.querySelector('*');
// If text looks like @username@domain, then it's a mention
if (/^@[^@]+(@[^@]+)?$/g.test(text)) {
if (MENTION_REGEX.test(text)) {
// Only show @username
const [_, username, domain] = text.split('@');
if (!hasChildren) {
@ -67,11 +99,11 @@ function _enhanceContent(content, opts = {}) {
link.classList.add('mention');
}
// If text looks like #hashtag, then it's a hashtag
if (/^#[^#]+$/g.test(text)) {
if (HASHTAG_REGEX.test(text)) {
if (!hasChildren) link.innerHTML = `#<span>${text.slice(1)}</span>`;
link.classList.add('mention', 'hashtag');
}
});
}
}
// EMOJIS
@ -80,18 +112,14 @@ function _enhanceContent(content, opts = {}) {
let textNodes;
if (enhancedContent.includes(':')) {
textNodes = extractTextNodes(dom);
textNodes.forEach((node) => {
let html = node.nodeValue
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
for (const node of textNodes) {
let html = escapeHTML(node.nodeValue);
if (emojis) {
html = emojifyText(html, emojis);
}
fauxDiv.innerHTML = html;
// const nodes = [...fauxDiv.childNodes];
node.replaceWith(...fauxDiv.childNodes);
});
}
}
// CODE BLOCKS
@ -99,31 +127,35 @@ function _enhanceContent(content, opts = {}) {
// Convert ```code``` to <pre><code>code</code></pre>
if (hasCodeBlock) {
const blocks = [...dom.querySelectorAll('p')].filter((p) =>
/^```[^]+```$/g.test(p.innerText.trim()),
CODE_BLOCK_REGEX.test(p.innerText.trim()),
);
blocks.forEach((block) => {
for (const block of blocks) {
const pre = document.createElement('pre');
// Replace <br /> with newlines
block.querySelectorAll('br').forEach((br) => br.replaceWith('\n'));
for (const br of block.querySelectorAll('br')) {
br.replaceWith('\n');
}
pre.innerHTML = `<code>${block.innerHTML.trim()}</code>`;
block.replaceWith(pre);
});
}
}
// Convert multi-paragraph code blocks to <pre><code>code</code></pre>
if (hasCodeBlock) {
const paragraphs = [...dom.querySelectorAll('p')];
// Filter out paragraphs with ``` in beginning only
const codeBlocks = paragraphs.filter((p) => /^```/g.test(p.innerText));
const codeBlocks = paragraphs.filter((p) =>
CODE_BLOCK_START_REGEX.test(p.innerText),
);
// For each codeBlocks, get all paragraphs until the last paragraph with ``` at the end only
codeBlocks.forEach((block) => {
for (const block of codeBlocks) {
const nextParagraphs = [block];
let hasCodeBlock = false;
let currentBlock = block;
while (currentBlock.nextElementSibling) {
const next = currentBlock.nextElementSibling;
if (next && next.tagName === 'P') {
if (/```$/g.test(next.innerText)) {
if (CODE_BLOCK_END_REGEX.test(next.innerText)) {
nextParagraphs.push(next);
hasCodeBlock = true;
break;
@ -137,16 +169,20 @@ function _enhanceContent(content, opts = {}) {
}
if (hasCodeBlock) {
const pre = document.createElement('pre');
nextParagraphs.forEach((p) => {
for (const p of nextParagraphs) {
// Replace <br /> with newlines
p.querySelectorAll('br').forEach((br) => br.replaceWith('\n'));
});
for (const br of p.querySelectorAll('br')) {
br.replaceWith('\n');
}
}
const codeText = nextParagraphs.map((p) => p.innerHTML).join('\n\n');
pre.innerHTML = `<code tabindex="0">${codeText}</code>`;
block.replaceWith(pre);
nextParagraphs.forEach((p) => p.remove());
for (const p of nextParagraphs) {
p.remove();
}
}
});
}
}
// INLINE CODE
@ -154,42 +190,36 @@ function _enhanceContent(content, opts = {}) {
// Convert `code` to <code>code</code>
if (enhancedContent.includes('`')) {
textNodes = extractTextNodes(dom);
textNodes.forEach((node) => {
let html = node.nodeValue
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
if (/`[^`]+`/g.test(html)) {
for (const node of textNodes) {
let html = escapeHTML(node.nodeValue);
if (INLINE_CODE_REGEX.test(html)) {
html = html.replaceAll(/(`[^]+?`)/g, '<code>$1</code>');
}
fauxDiv.innerHTML = html;
// const nodes = [...fauxDiv.childNodes];
node.replaceWith(...fauxDiv.childNodes);
});
}
}
// TWITTER USERNAMES
// =================
// Convert @username@twitter.com to <a href="https://twitter.com/username">@username@twitter.com</a>
if (/twitter\.com/i.test(enhancedContent)) {
if (TWITTER_DOMAIN_REGEX.test(enhancedContent)) {
textNodes = extractTextNodes(dom, {
rejectFilter: ['A'],
});
textNodes.forEach((node) => {
let html = node.nodeValue
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
if (/@[a-zA-Z0-9_]+@twitter\.com/g.test(html)) {
for (const node of textNodes) {
let html = escapeHTML(node.nodeValue);
if (TWITTER_MENTION_REGEX.test(html)) {
html = html.replaceAll(
/(@([a-zA-Z0-9_]+)@twitter\.com)/g,
TWITTER_MENTION_CAPTURE_REGEX,
'<a href="https://twitter.com/$2" rel="nofollow noopener noreferrer" target="_blank">$1</a>',
);
}
fauxDiv.innerHTML = html;
// const nodes = [...fauxDiv.childNodes];
node.replaceWith(...fauxDiv.childNodes);
});
}
}
// HASHTAG STUFFING
@ -235,22 +265,24 @@ function _enhanceContent(content, opts = {}) {
},
);
if (hashtagStuffedParagraphs?.length) {
hashtagStuffedParagraphs.forEach((p) => {
for (const p of hashtagStuffedParagraphs) {
p.classList.add('hashtag-stuffing');
p.title = p.innerText;
});
}
}
}
// ADD ASPECT RATIO TO ALL IMAGES
if (enhancedContent.includes('<img')) {
dom.querySelectorAll('img').forEach((img) => {
const imgs = dom.querySelectorAll('img');
for (let i = 0; i < imgs.length; i++) {
const img = imgs[i];
const width = img.getAttribute('width') || img.naturalWidth;
const height = img.getAttribute('height') || img.naturalHeight;
if (width && height) {
img.style.setProperty('--original-aspect-ratio', `${width}/${height}`);
}
});
}
}
if (postEnhanceDOM) {
@ -258,9 +290,7 @@ function _enhanceContent(content, opts = {}) {
// postEnhanceDOM(dom); // mutate dom
}
enhancedContent = dom.innerHTML;
return enhancedContent;
return returnDOM ? dom : dom.innerHTML;
}
const enhanceContent = mem(_enhanceContent);

View file

@ -3,5 +3,9 @@ import moize from 'moize';
window._moize = moize;
export default function mem(fn, opts = {}) {
return moize(fn, { ...opts, maxSize: 50, isDeepEqual: true });
return moize(fn, {
...opts,
maxSize: 30,
isDeepEqual: true,
});
}