From 5ae2058c07983d01454f473c0f3dd1548c0f4132 Mon Sep 17 00:00:00 2001 From: Mick O'Brien Date: Fri, 26 Apr 2024 12:23:53 +0100 Subject: [PATCH 1/9] Fix `enter` keyboard shortcut on timeline Currently pressing `enter` opens the active status if the status or any focusable child of the status is focused e.g. the avatar or a link. I think it should only open the post details when the post itself is focused. --- src/components/timeline.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/timeline.jsx b/src/components/timeline.jsx index 6fe1273..13f8a59 100644 --- a/src/components/timeline.jsx +++ b/src/components/timeline.jsx @@ -209,8 +209,8 @@ function Timeline({ const oRef = useHotkeys(['enter', 'o'], () => { // open active status - const activeItem = document.activeElement.closest(itemsSelector); - if (activeItem) { + const activeItem = document.activeElement; + if (activeItem?.matches(itemsSelector)) { activeItem.click(); } }); From daae055f4d1d376a64b3b67846f1e289641bed89 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Sun, 21 Apr 2024 07:51:09 +0800 Subject: [PATCH 2/9] List out forks --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 8f5c328..ffece20 100644 --- a/README.md +++ b/README.md @@ -244,6 +244,8 @@ And here I am. Building a Mastodon web client. ## Alternative web clients +- Phanpy forks ↓ + - [Agora](https://agorasocial.app/) - [Pinafore](https://pinafore.social/) ([retired](https://nolanlawson.com/2023/01/09/retiring-pinafore/)) - forks ↓ - [Semaphore](https://semaphore.social/) - [Enafore](https://enafore.social/) From 1f29aee26ebf1cc46174003995e13c7b38372e69 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Mon, 22 Apr 2024 16:41:37 +0800 Subject: [PATCH 3/9] Upgrade dependencies --- package-lock.json | 214 +++++++++++++++++++++++----------------------- package.json | 16 ++-- 2 files changed, 116 insertions(+), 114 deletions(-) diff --git a/package-lock.json b/package-lock.json index c7328de..8d3ce96 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "dependencies": { "@formatjs/intl-localematcher": "~0.5.4", "@formatjs/intl-segmenter": "~11.5.5", - "@formkit/auto-animate": "~0.8.1", + "@formkit/auto-animate": "~0.8.2", "@github/text-expander-element": "~2.6.1", "@iconify-icons/mingcute": "~1.2.9", "@justinribeiro/lite-youtube": "~1.5.0", @@ -25,14 +25,14 @@ "idb-keyval": "~6.2.1", "just-debounce-it": "~3.2.0", "lz-string": "~1.5.0", - "masto": "~6.7.0", + "masto": "~6.7.2", "moize": "~6.1.6", "p-retry": "~6.2.0", "p-throttle": "~6.1.0", - "preact": "~10.20.1", + "preact": "~10.20.2", "punycode": "~2.3.1", "react-hotkeys-hook": "~4.5.0", - "react-intersection-observer": "~9.8.1", + "react-intersection-observer": "~9.8.2", "react-quick-pinch-zoom": "~5.1.0", "react-router-dom": "6.6.2", "string-length": "6.0.0", @@ -48,13 +48,13 @@ "@preact/preset-vite": "~2.8.2", "@trivago/prettier-plugin-sort-imports": "~4.3.0", "postcss": "~8.4.38", - "postcss-dark-theme-class": "~1.2.1", - "postcss-preset-env": "~9.5.4", + "postcss-dark-theme-class": "~1.2.3", + "postcss-preset-env": "~9.5.8", "twitter-text": "~3.1.0", - "vite": "~5.2.8", + "vite": "~5.2.10", "vite-plugin-generate-file": "~0.1.1", "vite-plugin-html-config": "~1.0.11", - "vite-plugin-pwa": "~0.19.7", + "vite-plugin-pwa": "~0.19.8", "vite-plugin-remove-console": "~2.2.0", "workbox-cacheable-response": "~7.0.0", "workbox-expiration": "~7.0.0", @@ -1974,9 +1974,9 @@ } }, "node_modules/@csstools/color-helpers": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-4.1.0.tgz", - "integrity": "sha512-pWRKF6cDwget8HowIIf2MqEmqIca/cf8/jO4b3PRtUF5EfQXYMtBIKycXB4yXTCUmwLKOoRZAzh/hjnc7ywOIg==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-4.2.0.tgz", + "integrity": "sha512-hJJrSBzbfGxUsaR6X4Bzd/FLx0F1ulKnR5ljY9AiXCtsR+H+zSWQDFWlKES1BRaVZTDHLpIIHS9K2o0h+JLlrg==", "dev": true, "funding": [ { @@ -2016,9 +2016,9 @@ } }, "node_modules/@csstools/css-color-parser": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-1.6.3.tgz", - "integrity": "sha512-pQPUPo32HW3/NuZxrwr3VJHE+vGqSTVI5gK4jGbuJ7eOFUrsTmZikXcVdInCVWOvuxK5xbCzwDWoTlZUCAKN+A==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-2.0.0.tgz", + "integrity": "sha512-0/v6OPpcg+b8TJT2N1Rcp0oH5xEvVOU5K2qDkaR3IMHNXuJ7XfVCQLINt3Cuj8mr54DbilEoZ9uvAmHBoZ//Fw==", "dev": true, "funding": [ { @@ -2031,7 +2031,7 @@ } ], "dependencies": { - "@csstools/color-helpers": "^4.1.0", + "@csstools/color-helpers": "^4.2.0", "@csstools/css-calc": "^1.2.0" }, "engines": { @@ -2133,9 +2133,9 @@ } }, "node_modules/@csstools/postcss-color-function": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-3.0.13.tgz", - "integrity": "sha512-gM24cIPU45HSPJ2zllz7VKjS1OKQS1sKOMI7Wsw8gFyXSGAGrxhYo++McylOqOXd8ecMaKxKQMUJqJVibvJYig==", + "version": "3.0.14", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-3.0.14.tgz", + "integrity": "sha512-joGAf5bT3Jg1CpybupMJ4DwNg/VNjmLWZoWMDmX0MTy/ftHA1Qr4+CslqTT4AA1n6Dx4Wa+DSMGPrDLHtRP0jg==", "dev": true, "funding": [ { @@ -2148,7 +2148,7 @@ } ], "dependencies": { - "@csstools/css-color-parser": "^1.6.3", + "@csstools/css-color-parser": "^2.0.0", "@csstools/css-parser-algorithms": "^2.6.1", "@csstools/css-tokenizer": "^2.2.4", "@csstools/postcss-progressive-custom-properties": "^3.2.0", @@ -2162,9 +2162,9 @@ } }, "node_modules/@csstools/postcss-color-mix-function": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-2.0.13.tgz", - "integrity": "sha512-mD8IIfGVeWkN1H1wfCqYePOg4cDnVrOXm4P0OlYcvKriq6sImGCGShv/2D88q6s3iUlLXfUBES+DUjLVjDMhnw==", + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-2.0.14.tgz", + "integrity": "sha512-ZLbgtdhyuOoWoRo/W8jFv68q+IMgTJHOAI+WunRbrRPqI+vJ0K2rud/lS9Se5urzM/imVKs/kz0Uobm5Yj4HUg==", "dev": true, "funding": [ { @@ -2177,7 +2177,7 @@ } ], "dependencies": { - "@csstools/css-color-parser": "^1.6.3", + "@csstools/css-color-parser": "^2.0.0", "@csstools/css-parser-algorithms": "^2.6.1", "@csstools/css-tokenizer": "^2.2.4", "@csstools/postcss-progressive-custom-properties": "^3.2.0", @@ -2244,9 +2244,9 @@ } }, "node_modules/@csstools/postcss-gamut-mapping": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@csstools/postcss-gamut-mapping/-/postcss-gamut-mapping-1.0.6.tgz", - "integrity": "sha512-qGFpHU9cRf9qqkbHh9cWMTlBtGi/ujPgP/znQdwkbB4TgDR1ddI5wRRrksBsx64sfoUSlIEd70bxXzD9FtfdLg==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@csstools/postcss-gamut-mapping/-/postcss-gamut-mapping-1.0.7.tgz", + "integrity": "sha512-vrsHsl5TN6NB5CT0rPG6JE9V2GLFftcmPtF/k4cWT4gyVMCsDyS9wEVl82sgvh/JQ32TaUo6bh8Ndl+XRJqGQw==", "dev": true, "funding": [ { @@ -2259,7 +2259,7 @@ } ], "dependencies": { - "@csstools/css-color-parser": "^1.6.3", + "@csstools/css-color-parser": "^2.0.0", "@csstools/css-parser-algorithms": "^2.6.1", "@csstools/css-tokenizer": "^2.2.4" }, @@ -2271,9 +2271,9 @@ } }, "node_modules/@csstools/postcss-gradients-interpolation-method": { - "version": "4.0.14", - "resolved": "https://registry.npmjs.org/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-4.0.14.tgz", - "integrity": "sha512-VMWC3xtpchHJoRBb/fs1gJR/5nHopX+0GwwmgdCI1DjROtfWUKIW0nv8occ922Gv0/Lk93XBtYBv8JttVBMZUQ==", + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-4.0.15.tgz", + "integrity": "sha512-0xQ5r4WU/6W2lDmnOTx9liC1Cq6RSnrkEzqX7d0cRA3fz5hjC276pA0nLMoAiY3vtAp0u71nTk/3TRdnCx/OUw==", "dev": true, "funding": [ { @@ -2286,7 +2286,7 @@ } ], "dependencies": { - "@csstools/css-color-parser": "^1.6.3", + "@csstools/css-color-parser": "^2.0.0", "@csstools/css-parser-algorithms": "^2.6.1", "@csstools/css-tokenizer": "^2.2.4", "@csstools/postcss-progressive-custom-properties": "^3.2.0", @@ -2300,9 +2300,9 @@ } }, "node_modules/@csstools/postcss-hwb-function": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-3.0.12.tgz", - "integrity": "sha512-90kIs+FsM6isAXLVoFHTTl4h0J6g1J1M6ahpIjAs6/k7a2A9FB/q+l0MHpLre0ZiPlBf2y3e1j4L+79vml7kJw==", + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-3.0.13.tgz", + "integrity": "sha512-f44tgkFSxJBGm8UjlkAfBP7xE2x2XFFdvNdedHl8jpx2pQcW8a50OT3yeMnM3NB9Y2Ynd7Wn8iXARiV/IHoKvw==", "dev": true, "funding": [ { @@ -2315,7 +2315,7 @@ } ], "dependencies": { - "@csstools/css-color-parser": "^1.6.3", + "@csstools/css-color-parser": "^2.0.0", "@csstools/css-parser-algorithms": "^2.6.1", "@csstools/css-tokenizer": "^2.2.4", "@csstools/postcss-progressive-custom-properties": "^3.2.0", @@ -2655,9 +2655,9 @@ } }, "node_modules/@csstools/postcss-oklab-function": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-3.0.13.tgz", - "integrity": "sha512-xbzMmukDFAwCt2+279io7ZiamZj87s6cnU3UgKB3G+NMpRX9A6uvN8xlnTLCe384hqg6hix5vlOmwkxqACb5pg==", + "version": "3.0.14", + "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-3.0.14.tgz", + "integrity": "sha512-92xdpcfc2wB3z4+GftPA0PXMuGI/tRLw9Tc0+HzpaAHHxyLK6aCJtoQIcw0Ox/PthXtqXZn/3wWT/Idfe8I7Wg==", "dev": true, "funding": [ { @@ -2670,7 +2670,7 @@ } ], "dependencies": { - "@csstools/css-color-parser": "^1.6.3", + "@csstools/css-color-parser": "^2.0.0", "@csstools/css-parser-algorithms": "^2.6.1", "@csstools/css-tokenizer": "^2.2.4", "@csstools/postcss-progressive-custom-properties": "^3.2.0", @@ -2709,9 +2709,9 @@ } }, "node_modules/@csstools/postcss-relative-color-syntax": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-2.0.13.tgz", - "integrity": "sha512-mENWPNcHdiEYtjHFfZP9U1jNukQgFpSQ7wvTvwiadK3qgNBiSl0vMSinM9kKsGsJLTHQ0LEAqWLHurU52I4Jeg==", + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-2.0.14.tgz", + "integrity": "sha512-NlxgLjAjVCTUVGiWk8WNj3dKvux9eC6O5aLM3BmdA8UXEwBHYI9r4IqlanxG9PlcXnzhTUX6eZsqgmxwt4FPow==", "dev": true, "funding": [ { @@ -2724,7 +2724,7 @@ } ], "dependencies": { - "@csstools/css-color-parser": "^1.6.3", + "@csstools/css-color-parser": "^2.0.0", "@csstools/css-parser-algorithms": "^2.6.1", "@csstools/css-tokenizer": "^2.2.4", "@csstools/postcss-progressive-custom-properties": "^3.2.0", @@ -2790,9 +2790,9 @@ } }, "node_modules/@csstools/postcss-text-decoration-shorthand": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-3.0.5.tgz", - "integrity": "sha512-qKxXpD0TYINkUtWDN1RHdeWKtZCzEv5j3UMT/ZGqyY27icwCFw7iKO0bUeLSHjYFBqhurCWvoOsa9REqLdrNDw==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-3.0.6.tgz", + "integrity": "sha512-Q8HEu4AEiwNVZBD6+DpQ8M9SajpMow4+WtmndWIAv8qxDtDYL4JK1xXWkhOGk28PrcJawOvkrEZ8Ri59UN1TJw==", "dev": true, "funding": [ { @@ -2805,7 +2805,7 @@ } ], "dependencies": { - "@csstools/color-helpers": "^4.1.0", + "@csstools/color-helpers": "^4.2.0", "postcss-value-parser": "^4.2.0" }, "engines": { @@ -3326,10 +3326,9 @@ } }, "node_modules/@formkit/auto-animate": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@formkit/auto-animate/-/auto-animate-0.8.1.tgz", - "integrity": "sha512-0/Z2cuNXWVVIG/l0SpcHAWFhGdvLJ8DRvEfRWvmojtmRWfEy+LWNwgDazbZqY0qQYtkHcoEK3jBLkhiZaB/4Ig==", - "license": "MIT" + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/@formkit/auto-animate/-/auto-animate-0.8.2.tgz", + "integrity": "sha512-SwPWfeRa5veb1hOIBMdzI+73te5puUBHmqqaF1Bu7FjvxlYSz/kJcZKSa9Cg60zL0uRNeJL2SbRxV6Jp6Q1nFQ==" }, "node_modules/@github/combobox-nav": { "version": "2.1.5", @@ -4469,9 +4468,9 @@ } }, "node_modules/css-blank-pseudo": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-6.0.1.tgz", - "integrity": "sha512-goSnEITByxTzU4Oh5oJZrEWudxTqk7L6IXj1UW69pO6Hv0UdX+Vsrt02FFu5DweRh2bLu6WpX/+zsQCu5O1gKw==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-6.0.2.tgz", + "integrity": "sha512-J/6m+lsqpKPqWHOifAFtKFeGLOzw3jR92rxQcwRUfA/eTuZzKfKlxOmYDx2+tqOPQAueNvBiY8WhAeHu5qNmTg==", "dev": true, "funding": [ { @@ -6031,9 +6030,9 @@ } }, "node_modules/masto": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/masto/-/masto-6.7.0.tgz", - "integrity": "sha512-R1UyuCdiyBuA9xuIEVIYa2187oIoHhpL1T0glIY+RICAo7JYOAEPdi4aAmROyPcWOYwMlaVDmRRb1zmNbvTnVg==", + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/masto/-/masto-6.7.2.tgz", + "integrity": "sha512-MEX/XDWLJyOjCOedhGZaIsrTIH9yIA1k+z2ySJkClIfphtv8DTulBpcpi8CnBMCvgR/RjAF/wdu3QL5p6BVe2A==", "dependencies": { "change-case": "^4.1.2", "events-to-async": "^2.0.1", @@ -6424,9 +6423,9 @@ } }, "node_modules/postcss-color-functional-notation": { - "version": "6.0.8", - "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-6.0.8.tgz", - "integrity": "sha512-BilFPTHcfWEnuQeqL83nbSPVK3tcU57S60aOrqgditarNDzOojyF0Gdc2Ur5L+zox366QjrCe0rOBLDO2pNvRQ==", + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-6.0.9.tgz", + "integrity": "sha512-8i/ofOArZ4fljp+3g+HI6Pok01Kb8YaSqInrJt2vMimEKrI0ZDNRLpH+wLhXBNu/Bi8zeWDvxhvCqsGSpu8E6Q==", "dev": true, "funding": [ { @@ -6439,7 +6438,7 @@ } ], "dependencies": { - "@csstools/css-color-parser": "^1.6.3", + "@csstools/css-color-parser": "^2.0.0", "@csstools/css-parser-algorithms": "^2.6.1", "@csstools/css-tokenizer": "^2.2.4", "@csstools/postcss-progressive-custom-properties": "^3.2.0", @@ -6533,9 +6532,9 @@ } }, "node_modules/postcss-custom-properties": { - "version": "13.3.6", - "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-13.3.6.tgz", - "integrity": "sha512-vVVIwQbJiIz+PBLMIWA6XMi53Zg66/f474KolA7x0Das6EwkATc/9ZvM6zZx2gs7ZhcgVHjmWBbHkK9FlCgLeA==", + "version": "13.3.7", + "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-13.3.7.tgz", + "integrity": "sha512-0N9F/GUCr/D0IazjzHahyYW2bQVDT6qDtEudiGHAhMd3XqhfM3VmfYVlkc/40DOhsPtngSNb54/Ctu8msvFOvQ==", "dev": true, "funding": [ { @@ -6590,9 +6589,9 @@ } }, "node_modules/postcss-dark-theme-class": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/postcss-dark-theme-class/-/postcss-dark-theme-class-1.2.1.tgz", - "integrity": "sha512-EzQMGOcYnE0eMBjfgB+xnamlZ8O02Aiojyg/iv84cpRUdLKZW8ankZWxWUhhIid1OF7yl4G3BeYfE+7CGY2tdQ==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/postcss-dark-theme-class/-/postcss-dark-theme-class-1.2.3.tgz", + "integrity": "sha512-Hr3pf9naYw0AJmIv6BcnkkpeWtIbXNc8OGqLbyYtHLqbuBpue5g7qK9g8fY7qvl3jig6tyu+Ga6N1FpMjIYqBQ==", "dev": true, "funding": [ { @@ -6604,6 +6603,9 @@ "url": "https://github.com/sponsors/ai" } ], + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, "engines": { "node": ">=18.0" }, @@ -6772,9 +6774,9 @@ } }, "node_modules/postcss-lab-function": { - "version": "6.0.13", - "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-6.0.13.tgz", - "integrity": "sha512-tzEThi3prSyomnVqaAU+k/YJib4rxeeTKVfMt+mPcEugFgp0t6xRjoc7fzaWCoEwYLC6GxGLD8/Ugx8COCqabw==", + "version": "6.0.14", + "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-6.0.14.tgz", + "integrity": "sha512-ddQS9FRWT8sfl4wfW0ae8fpP2JdLIuhC9pYpHq1077avjrLzg73T9IEVu5QmFa72nJhYFlO9CbqjcoSdEzfY9A==", "dev": true, "funding": [ { @@ -6787,7 +6789,7 @@ } ], "dependencies": { - "@csstools/css-color-parser": "^1.6.3", + "@csstools/css-color-parser": "^2.0.0", "@csstools/css-parser-algorithms": "^2.6.1", "@csstools/css-tokenizer": "^2.2.4", "@csstools/postcss-progressive-custom-properties": "^3.2.0", @@ -6826,9 +6828,9 @@ } }, "node_modules/postcss-nesting": { - "version": "12.1.1", - "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-12.1.1.tgz", - "integrity": "sha512-qc74KvIAQNa5ujZKG1UV286dhaDW6basbUy2i9AzNU/T8C9hpvGu9NZzm1SfePe2yP7sPYgpA8d4sPVopn2Hhw==", + "version": "12.1.2", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-12.1.2.tgz", + "integrity": "sha512-FUmTHGDNundodutB4PUBxt/EPuhgtpk8FJGRsBhOuy+6FnkR2A8RZWIsyyy6XmhvX2DZQQWIkvu+HB4IbJm+Ew==", "dev": true, "funding": [ { @@ -6936,9 +6938,9 @@ } }, "node_modules/postcss-preset-env": { - "version": "9.5.4", - "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-9.5.4.tgz", - "integrity": "sha512-o/jOlJjhm4f6rI5q1f+4Og3tz1cjaO50er9ndk7ZdcXHjWOH49kMAhqDC/nQifypQkOAiAmF46dPt3pZM+Cwbg==", + "version": "9.5.8", + "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-9.5.8.tgz", + "integrity": "sha512-AjQ5ZgrVJVL8Ja7UxIlbyHUN5knkdk6L0JxxV7KDZU4S5WfJjRNgvWlU7Xq7CMyRZSjHZBmLDOsBKIL9WStPyw==", "dev": true, "funding": [ { @@ -6952,13 +6954,13 @@ ], "dependencies": { "@csstools/postcss-cascade-layers": "^4.0.4", - "@csstools/postcss-color-function": "^3.0.13", - "@csstools/postcss-color-mix-function": "^2.0.13", + "@csstools/postcss-color-function": "^3.0.14", + "@csstools/postcss-color-mix-function": "^2.0.14", "@csstools/postcss-exponential-functions": "^1.0.5", "@csstools/postcss-font-format-keywords": "^3.0.2", - "@csstools/postcss-gamut-mapping": "^1.0.6", - "@csstools/postcss-gradients-interpolation-method": "^4.0.14", - "@csstools/postcss-hwb-function": "^3.0.12", + "@csstools/postcss-gamut-mapping": "^1.0.7", + "@csstools/postcss-gradients-interpolation-method": "^4.0.15", + "@csstools/postcss-hwb-function": "^3.0.13", "@csstools/postcss-ic-unit": "^3.0.6", "@csstools/postcss-initial": "^1.0.1", "@csstools/postcss-is-pseudo-class": "^4.0.6", @@ -6972,27 +6974,27 @@ "@csstools/postcss-media-queries-aspect-ratio-number-values": "^2.0.7", "@csstools/postcss-nested-calc": "^3.0.2", "@csstools/postcss-normalize-display-values": "^3.0.2", - "@csstools/postcss-oklab-function": "^3.0.13", + "@csstools/postcss-oklab-function": "^3.0.14", "@csstools/postcss-progressive-custom-properties": "^3.2.0", - "@csstools/postcss-relative-color-syntax": "^2.0.13", + "@csstools/postcss-relative-color-syntax": "^2.0.14", "@csstools/postcss-scope-pseudo-class": "^3.0.1", "@csstools/postcss-stepped-value-functions": "^3.0.6", - "@csstools/postcss-text-decoration-shorthand": "^3.0.5", + "@csstools/postcss-text-decoration-shorthand": "^3.0.6", "@csstools/postcss-trigonometric-functions": "^3.0.6", "@csstools/postcss-unset-value": "^3.0.1", "autoprefixer": "^10.4.19", "browserslist": "^4.22.3", - "css-blank-pseudo": "^6.0.1", + "css-blank-pseudo": "^6.0.2", "css-has-pseudo": "^6.0.3", "css-prefers-color-scheme": "^9.0.1", "cssdb": "^8.0.0", "postcss-attribute-case-insensitive": "^6.0.3", "postcss-clamp": "^4.1.0", - "postcss-color-functional-notation": "^6.0.8", + "postcss-color-functional-notation": "^6.0.9", "postcss-color-hex-alpha": "^9.0.4", "postcss-color-rebeccapurple": "^9.0.3", "postcss-custom-media": "^10.0.4", - "postcss-custom-properties": "^13.3.6", + "postcss-custom-properties": "^13.3.7", "postcss-custom-selectors": "^7.1.8", "postcss-dir-pseudo-class": "^8.0.1", "postcss-double-position-gradients": "^5.0.6", @@ -7001,14 +7003,14 @@ "postcss-font-variant": "^5.0.0", "postcss-gap-properties": "^5.0.1", "postcss-image-set-function": "^6.0.3", - "postcss-lab-function": "^6.0.13", + "postcss-lab-function": "^6.0.14", "postcss-logical": "^7.0.1", - "postcss-nesting": "^12.1.1", + "postcss-nesting": "^12.1.2", "postcss-opacity-percentage": "^2.0.0", "postcss-overflow-shorthand": "^5.0.1", "postcss-page-break": "^3.0.4", "postcss-place": "^9.0.1", - "postcss-pseudo-class-any-link": "^9.0.1", + "postcss-pseudo-class-any-link": "^9.0.2", "postcss-replace-overflow-wrap": "^4.0.0", "postcss-selector-not": "^7.0.2" }, @@ -7020,9 +7022,9 @@ } }, "node_modules/postcss-pseudo-class-any-link": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-9.0.1.tgz", - "integrity": "sha512-cKYGGZ9yzUZi+dZd7XT2M8iSDfo+T2Ctbpiizf89uBTBfIpZpjvTavzIJXpCReMVXSKROqzpxClNu6fz4DHM0Q==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-9.0.2.tgz", + "integrity": "sha512-HFSsxIqQ9nA27ahyfH37cRWGk3SYyQLpk0LiWw/UGMV4VKT5YG2ONee4Pz/oFesnK0dn2AjcyequDbIjKJgB0g==", "dev": true, "funding": [ { @@ -7100,9 +7102,9 @@ "license": "MIT" }, "node_modules/preact": { - "version": "10.20.1", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.20.1.tgz", - "integrity": "sha512-JIFjgFg9B2qnOoGiYMVBtrcFxHqn+dNXbq76bVmcaHYJFYR4lW67AOcXgAYQQTDYXDOg/kTZrKPNCdRgJ2UJmw==", + "version": "10.20.2", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.20.2.tgz", + "integrity": "sha512-S1d1ernz3KQ+Y2awUxKakpfOg2CEmJmwOP+6igPx6dgr6pgDvenqYviyokWso2rhHvGtTlWWnJDa7RaPbQerTg==", "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" @@ -7230,9 +7232,9 @@ } }, "node_modules/react-intersection-observer": { - "version": "9.8.1", - "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.8.1.tgz", - "integrity": "sha512-QzOFdROX8D8MH3wE3OVKH0f3mLjKTtEN1VX/rkNuECCff+aKky0pIjulDhr3Ewqj5el/L+MhBkM3ef0Tbt+qUQ==", + "version": "9.8.2", + "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.8.2.tgz", + "integrity": "sha512-901naEiiZmse3p+AmtbQ3NL9xx+gQ8TXLiGDc+8GiE3JKJkNV3vP737aGuWTAXBA+1QqxPrDDE+fIEgYpGDlrQ==", "peerDependencies": { "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" @@ -8207,9 +8209,9 @@ } }, "node_modules/vite": { - "version": "5.2.8", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.8.tgz", - "integrity": "sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA==", + "version": "5.2.10", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.10.tgz", + "integrity": "sha512-PAzgUZbP7msvQvqdSD+ErD5qGnSFiGOoWmV5yAKUEI0kdhjbH6nMWVyZQC/hSc4aXwc0oJ9aEdIiF9Oje0JFCw==", "dev": true, "dependencies": { "esbuild": "^0.20.1", @@ -8288,9 +8290,9 @@ } }, "node_modules/vite-plugin-pwa": { - "version": "0.19.7", - "resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-0.19.7.tgz", - "integrity": "sha512-18TECxoGPQE7tVZzKxbf5Icrl5688n1JGMPSgGotTsh89vLDxevY7ICfD3CFVfonZXh8ckuyJXg0NXE5+FAl2A==", + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-0.19.8.tgz", + "integrity": "sha512-e1oK0dfhzhDhY3VBuML6c0h8Xfx6EkOVYqolj7g+u8eRfdauZe5RLteCIA/c5gH0CBQ0CNFAuv/AFTx4Z7IXTw==", "dev": true, "dependencies": { "debug": "^4.3.4", diff --git a/package.json b/package.json index a6f2497..c8485d5 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "dependencies": { "@formatjs/intl-localematcher": "~0.5.4", "@formatjs/intl-segmenter": "~11.5.5", - "@formkit/auto-animate": "~0.8.1", + "@formkit/auto-animate": "~0.8.2", "@github/text-expander-element": "~2.6.1", "@iconify-icons/mingcute": "~1.2.9", "@justinribeiro/lite-youtube": "~1.5.0", @@ -27,14 +27,14 @@ "idb-keyval": "~6.2.1", "just-debounce-it": "~3.2.0", "lz-string": "~1.5.0", - "masto": "~6.7.0", + "masto": "~6.7.2", "moize": "~6.1.6", "p-retry": "~6.2.0", "p-throttle": "~6.1.0", - "preact": "~10.20.1", + "preact": "~10.20.2", "punycode": "~2.3.1", "react-hotkeys-hook": "~4.5.0", - "react-intersection-observer": "~9.8.1", + "react-intersection-observer": "~9.8.2", "react-quick-pinch-zoom": "~5.1.0", "react-router-dom": "6.6.2", "string-length": "6.0.0", @@ -50,13 +50,13 @@ "@preact/preset-vite": "~2.8.2", "@trivago/prettier-plugin-sort-imports": "~4.3.0", "postcss": "~8.4.38", - "postcss-dark-theme-class": "~1.2.1", - "postcss-preset-env": "~9.5.4", + "postcss-dark-theme-class": "~1.2.3", + "postcss-preset-env": "~9.5.8", "twitter-text": "~3.1.0", - "vite": "~5.2.8", + "vite": "~5.2.10", "vite-plugin-generate-file": "~0.1.1", "vite-plugin-html-config": "~1.0.11", - "vite-plugin-pwa": "~0.19.7", + "vite-plugin-pwa": "~0.19.8", "vite-plugin-remove-console": "~2.2.0", "workbox-cacheable-response": "~7.0.0", "workbox-expiration": "~7.0.0", From c8dc32b884cee33142066b99206e43c422d83287 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Mon, 22 Apr 2024 16:42:12 +0800 Subject: [PATCH 4/9] Test caching shazam states --- src/components/lazy-shazam.jsx | 9 +++++++-- src/components/status.jsx | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/components/lazy-shazam.jsx b/src/components/lazy-shazam.jsx index b50c46d..4e441be 100644 --- a/src/components/lazy-shazam.jsx +++ b/src/components/lazy-shazam.jsx @@ -7,10 +7,13 @@ import { useInView } from 'react-intersection-observer'; // The sticky header, usually at the top const TOP = 48; -export default function LazyShazam({ children }) { +const shazamIDs = {}; + +export default function LazyShazam({ id, children }) { const containerRef = useRef(); + const hasID = !!shazamIDs[id]; const [visible, setVisible] = useState(false); - const [visibleStart, setVisibleStart] = useState(false); + const [visibleStart, setVisibleStart] = useState(hasID || false); const { ref } = useInView({ root: null, @@ -20,6 +23,7 @@ export default function LazyShazam({ children }) { onChange: (inView) => { if (inView) { setVisible(true); + if (id) shazamIDs[id] = true; } }, triggerOnce: true, @@ -35,6 +39,7 @@ export default function LazyShazam({ children }) { } else { setVisibleStart(true); } + if (id) shazamIDs[id] = true; } }, []); diff --git a/src/components/status.jsx b/src/components/status.jsx index c359872..c8e3fae 100644 --- a/src/components/status.jsx +++ b/src/components/status.jsx @@ -3337,7 +3337,7 @@ const QuoteStatuses = memo(({ id, instance, level = 0 }) => { return uniqueQuotes.map((q) => { return ( - + Date: Thu, 25 Apr 2024 13:59:01 +0800 Subject: [PATCH 5/9] initStates needed for standalone compose page --- src/compose.jsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/compose.jsx b/src/compose.jsx index 9f5a898..4f2607b 100644 --- a/src/compose.jsx +++ b/src/compose.jsx @@ -7,6 +7,7 @@ import { lazy } from 'preact/compat'; import { useEffect, useState } from 'preact/hooks'; import IntlSegmenterSuspense from './components/intl-segmenter-suspense'; +import { initStates } from './utils/states'; // import Compose from './components/compose'; import useTitle from './utils/useTitle'; @@ -31,6 +32,10 @@ function App() { : 'Compose', ); + useEffect(() => { + initStates(); + }, []); + useEffect(() => { if (uiState === 'closed') { try { From 11e64a2cc4714b1aefd21fbff51c35f53a6bfa75 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Fri, 26 Apr 2024 09:58:07 +0800 Subject: [PATCH 6/9] Fix filter expiry wrongly set if there's no expiry --- src/pages/filters.jsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/pages/filters.jsx b/src/pages/filters.jsx index a0c0511..0d83d27 100644 --- a/src/pages/filters.jsx +++ b/src/pages/filters.jsx @@ -286,7 +286,13 @@ function FiltersAddEdit({ filter, onClose }) { // Preserve existing expiry if not specified // Seconds from now to expiresAtDate // Other clients don't do this - expiresIn = Math.floor((expiresAtDate - new Date()) / 1000); + if (hasExpiry) { + expiresIn = Math.floor( + (expiresAtDate - new Date()) / 1000, + ); + } else { + expiresIn = null; + } } else if (expiresIn === '0' || expiresIn === 0) { // 0 = Never expiresIn = null; From 77bc06545c5279255b80f619d497c5aec9573f6e Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Wed, 1 May 2024 15:05:29 +0800 Subject: [PATCH 7/9] Handle inline images --- src/components/status.css | 6 ++++++ src/utils/enhance-content.js | 11 +++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/components/status.css b/src/components/status.css index a472bbe..247600c 100644 --- a/src/components/status.css +++ b/src/components/status.css @@ -825,6 +825,12 @@ .timeline-deck .status .content.truncated ~ .card { display: none; } +.status .content .inner-content { + > img[height] { + height: auto; + aspect-ratio: var(--original-aspect-ratio); + } +} .status .content .inner-content a:not(.mention, .has-url-text) { color: var(--link-text-color); } diff --git a/src/utils/enhance-content.js b/src/utils/enhance-content.js index d615735..4a30481 100644 --- a/src/utils/enhance-content.js +++ b/src/utils/enhance-content.js @@ -242,6 +242,17 @@ function _enhanceContent(content, opts = {}) { } } + // ADD ASPECT RATIO TO ALL IMAGES + if (enhancedContent.includes(' { + 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) { queueMicrotask(() => postEnhanceDOM(dom)); // postEnhanceDOM(dom); // mutate dom From 65a4c3441c4266b6236749a5461edb78913f28ad Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Thu, 2 May 2024 00:14:25 +0800 Subject: [PATCH 8/9] Add search for custom emojis --- package-lock.json | 9 + package.json | 1 + src/components/compose.css | 152 ++++++++++---- src/components/compose.jsx | 417 +++++++++++++++++++++++++------------ src/components/status.css | 4 +- 5 files changed, 411 insertions(+), 172 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8d3ce96..ac84dd3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "dayjs-twitter": "~0.5.0", "fast-blurhash": "~1.1.2", "fast-equals": "~5.0.1", + "fuse.js": "~7.0.0", "html-prettify": "^1.0.7", "idb-keyval": "~6.2.1", "just-debounce-it": "~3.2.0", @@ -5130,6 +5131,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/fuse.js": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.0.0.tgz", + "integrity": "sha512-14F4hBIxqKvD4Zz/XjDc3y94mNZN6pRv3U13Udo0lNLCWRBUsrMv2xwcF/y/Z5sV6+FQW+/ow68cHpm4sunt8Q==", + "engines": { + "node": ">=10" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", diff --git a/package.json b/package.json index c8485d5..907f901 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "dayjs-twitter": "~0.5.0", "fast-blurhash": "~1.1.2", "fast-equals": "~5.0.1", + "fuse.js": "~7.0.0", "html-prettify": "^1.0.7", "idb-keyval": "~6.2.1", "just-debounce-it": "~3.2.0", diff --git a/src/components/compose.css b/src/components/compose.css index 407f172..220bcf4 100644 --- a/src/components/compose.css +++ b/src/components/compose.css @@ -597,41 +597,123 @@ #custom-emojis-sheet { max-height: 50vh; max-height: 50dvh; -} -#custom-emojis-sheet main { - mask-image: none; -} -#custom-emojis-sheet .custom-emojis-list .section-header { - font-size: 80%; - text-transform: uppercase; - color: var(--text-insignificant-color); - padding: 8px 0 4px; - position: sticky; - top: 0; - background-color: var(--bg-blur-color); - backdrop-filter: blur(1px); -} -#custom-emojis-sheet .custom-emojis-list section { - display: flex; - flex-wrap: wrap; -} -#custom-emojis-sheet .custom-emojis-list button { - border-radius: 8px; - background-image: radial-gradient( - closest-side, - var(--img-bg-color), - transparent - ); -} -#custom-emojis-sheet .custom-emojis-list button:is(:hover, :focus) { - filter: none; - background-color: var(--bg-faded-color); -} -#custom-emojis-sheet .custom-emojis-list button img { - transition: transform 0.1s ease-out; -} -#custom-emojis-sheet .custom-emojis-list button:is(:hover, :focus) img { - transform: scale(1.5); + + header { + .loader-container { + margin: 0; + } + + form { + margin: 8px 0 0; + + input { + width: 100%; + min-width: 0; + font-size: 0.8em; + } + } + } + + main { + mask-image: none; + min-height: 40vh; + padding-bottom: 88px; + } + + .custom-emojis-matches { + margin: 0; + padding: 0; + list-style: none; + display: flex; + flex-wrap: wrap; + } + + .custom-emojis-list { + .section-header { + font-size: 80%; + text-transform: uppercase; + color: var(--text-insignificant-color); + padding: 8px 0 4px; + position: sticky; + top: 0; + background-color: var(--bg-color); + z-index: 1; + } + section { + display: flex; + flex-wrap: wrap; + } + button { + color: var(--text-color); + border-radius: 8px; + background-image: radial-gradient( + closest-side, + var(--img-bg-color), + transparent + ); + text-shadow: 0 1px 0 var(--bg-color); + position: relative; + min-width: 44px; + min-height: 44px; + font-variant-numeric: slashed-zero; + font-feature-settings: 'ss01'; + + &[data-title]:after { + max-width: 50vw; + pointer-events: none; + position: absolute; + content: attr(data-title); + left: 50%; + top: 0; + background-color: var(--bg-color); + padding: 2px 4px; + border-radius: 4px; + font-size: 12px; + border: 1px solid var(--text-color); + transform: translate(-50%, -110%); + opacity: 0; + transition: opacity 0.1s ease-out 0.1s; + font-family: var(--monospace-font); + line-height: 1; + } + &.edge-left[data-title]:after { + left: 0; + transform: translate(0, -110%); + } + &.edge-right[data-title]:after { + left: 100%; + transform: translate(-100%, -110%); + } + + &:is(:hover, :focus) { + z-index: 1; + filter: none; + background-color: var(--bg-faded-color); + + &[data-title]:after { + opacity: 1; + } + } + + img { + transition: transform 0.1s ease-out; + } + + &:is(:hover, :focus) img { + transform: scale(2); + } + &.edge-left img { + transform-origin: left center; + } + &.edge-right img { + transform-origin: right center; + } + + code { + font-size: 0.8em; + } + } + } } .compose-field-container { diff --git a/src/components/compose.jsx b/src/components/compose.jsx index 49f9a84..12045bd 100644 --- a/src/components/compose.jsx +++ b/src/components/compose.jsx @@ -3,8 +3,16 @@ import './compose.css'; import '@github/text-expander-element'; import { MenuItem } from '@szhsin/react-menu'; import { deepEqual } from 'fast-equals'; +import Fuse from 'fuse.js'; +import { memo } from 'preact/compat'; import { forwardRef } from 'preact/compat'; -import { useEffect, useMemo, useRef, useState } from 'preact/hooks'; +import { + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from 'preact/hooks'; import { useHotkeys } from 'react-hotkeys-hook'; import stringLength from 'string-length'; import { uid } from 'uid/single'; @@ -21,6 +29,7 @@ import db from '../utils/db'; import emojifyText from '../utils/emojify-text'; import localeMatch from '../utils/locale-match'; import openCompose from '../utils/open-compose'; +import pmem from '../utils/pmem'; import shortenNumber from '../utils/shorten-number'; import showToast from '../utils/show-toast'; import states, { saveStatus } from '../utils/states'; @@ -181,6 +190,8 @@ function highlightText(text, { maxCharacters = Infinity }) { const rtf = new Intl.RelativeTimeFormat(); +const CUSTOM_EMOJIS_COUNT = 100; + function Compose({ onClose, replyToStatus, @@ -1423,25 +1434,40 @@ function autoResizeTextarea(textarea) { } } +async function _getCustomEmojis(instance, masto) { + const emojis = await masto.v1.customEmojis.list(); + const visibleEmojis = emojis.filter((e) => e.visibleInPicker); + const searcher = new Fuse(visibleEmojis, { + keys: ['shortcode'], + findAllMatches: true, + }); + return [visibleEmojis, searcher]; +} +const getCustomEmojis = pmem(_getCustomEmojis, { + // Limit by time to reduce memory usage + // Cached by instance + matchesArg: (cacheKeyArg, keyArg) => cacheKeyArg.instance === keyArg.instance, + maxAge: 30 * 60 * 1000, // 30 minutes +}); + const Textarea = forwardRef((props, ref) => { - const { masto } = api(); + const { masto, instance } = api(); const [text, setText] = useState(ref.current?.value || ''); const { maxCharacters, performSearch = () => {}, ...textareaProps } = props; // const snapStates = useSnapshot(states); // const charCount = snapStates.composerCharacterCount; - const customEmojis = useRef(); + // const customEmojis = useRef(); + const searcherRef = useRef(); useEffect(() => { - (async () => { - try { - const emojis = await masto.v1.customEmojis.list(); - console.log({ emojis }); - customEmojis.current = emojis; - } catch (e) { - // silent fail + getCustomEmojis(instance, masto) + .then((r) => { + const [emojis, searcher] = r; + searcherRef.current = searcher; + }) + .catch((e) => { console.error(e); - } - })(); + }); }, []); const textExpanderRef = useRef(); @@ -1467,23 +1493,26 @@ const Textarea = forwardRef((props, ref) => { // const emojis = customEmojis.current.filter((emoji) => // emoji.shortcode.startsWith(text), // ); - const emojis = filterShortcodes(customEmojis.current, text); + // const emojis = filterShortcodes(customEmojis.current, text); + const results = searcherRef.current?.search(text, { + limit: 5, + }); let html = ''; - emojis.forEach((emoji) => { + results.forEach(({ item: emoji }) => { const { shortcode, url } = emoji; html += `
  • - :${encodeHTML(shortcode)}: + ${encodeHTML(shortcode)}
  • `; }); // console.log({ emojis, html }); menu.innerHTML = html; provide( Promise.resolve({ - matched: emojis.length > 0, + matched: results.length > 0, fragment: menu, }), ); @@ -2185,38 +2214,19 @@ function CustomEmojisModal({ }) { const [uiState, setUIState] = useState('default'); const customEmojisList = useRef([]); - const [customEmojis, setCustomEmojis] = useState({}); + const [customEmojis, setCustomEmojis] = useState([]); const recentlyUsedCustomEmojis = useMemo( () => store.account.get('recentlyUsedCustomEmojis') || [], ); + const searcherRef = useRef(); useEffect(() => { setUIState('loading'); (async () => { try { - const emojis = await masto.v1.customEmojis.list(); - // Group emojis by category - const emojisCat = { - '--recent--': recentlyUsedCustomEmojis.filter((emoji) => - emojis.find((e) => e.shortcode === emoji.shortcode), - ), - }; - const othersCat = []; - emojis.forEach((emoji) => { - if (!emoji.visibleInPicker) return; - customEmojisList.current?.push?.(emoji); - if (!emoji.category) { - othersCat.push(emoji); - return; - } - if (!emojisCat[emoji.category]) { - emojisCat[emoji.category] = []; - } - emojisCat[emoji.category].push(emoji); - }); - if (othersCat.length) { - emojisCat['--others--'] = othersCat; - } - setCustomEmojis(emojisCat); + const [emojis, searcher] = await getCustomEmojis(instance, masto); + console.log('emojis', emojis); + searcherRef.current = searcher; + setCustomEmojis(emojis); setUIState('default'); } catch (e) { setUIState('error'); @@ -2225,6 +2235,83 @@ function CustomEmojisModal({ })(); }, []); + const customEmojisCatList = useMemo(() => { + // Group emojis by category + const emojisCat = { + '--recent--': recentlyUsedCustomEmojis.filter((emoji) => + customEmojis.find((e) => e.shortcode === emoji.shortcode), + ), + }; + const othersCat = []; + customEmojis.forEach((emoji) => { + customEmojisList.current?.push?.(emoji); + if (!emoji.category) { + othersCat.push(emoji); + return; + } + if (!emojisCat[emoji.category]) { + emojisCat[emoji.category] = []; + } + emojisCat[emoji.category].push(emoji); + }); + if (othersCat.length) { + emojisCat['--others--'] = othersCat; + } + return emojisCat; + }, [customEmojis]); + + const scrollableRef = useRef(); + const [matches, setMatches] = useState(null); + const onFind = useCallback( + (e) => { + const { value } = e.target; + if (value) { + const results = searcherRef.current?.search(value, { + limit: CUSTOM_EMOJIS_COUNT, + }); + setMatches(results.map((r) => r.item)); + scrollableRef.current?.scrollTo?.(0, 0); + } else { + setMatches(null); + } + }, + [customEmojis], + ); + + const onSelectEmoji = useCallback( + (emoji) => { + onSelect?.(emoji); + onClose?.(); + + queueMicrotask(() => { + let recentlyUsedCustomEmojis = + store.account.get('recentlyUsedCustomEmojis') || []; + const recentlyUsedEmojiIndex = recentlyUsedCustomEmojis.findIndex( + (e) => e.shortcode === emoji.shortcode, + ); + if (recentlyUsedEmojiIndex !== -1) { + // Move emoji to index 0 + recentlyUsedCustomEmojis.splice(recentlyUsedEmojiIndex, 1); + recentlyUsedCustomEmojis.unshift(emoji); + } else { + recentlyUsedCustomEmojis.unshift(emoji); + // Remove unavailable ones + recentlyUsedCustomEmojis = recentlyUsedCustomEmojis.filter((e) => + customEmojisList.current?.find?.( + (emoji) => emoji.shortcode === e.shortcode, + ), + ); + // Limit to 10 + recentlyUsedCustomEmojis = recentlyUsedCustomEmojis.slice(0, 10); + } + + // Store back + store.account.set('recentlyUsedCustomEmojis', recentlyUsedCustomEmojis); + }); + }, + [onSelect], + ); + return (
    {!!onClose && ( @@ -2233,107 +2320,167 @@ function CustomEmojisModal({ )}
    - Custom emojis{' '} - {uiState === 'loading' ? ( - - ) : ( - • {instance} - )} -
    -
    -
    - {uiState === 'error' && ( -
    -

    Error loading custom emojis

    -
    +
    + Custom emojis{' '} + {uiState === 'loading' ? ( + + ) : ( + • {instance} )} - {uiState === 'default' && - Object.entries(customEmojis).map( - ([category, emojis]) => - !!emojis?.length && ( - <> -
    - {{ - '--recent--': 'Recently used', - '--others--': 'Others', - }[category] || category} -
    -
    - {emojis.map((emoji) => ( - - ))} -
    - - ), - )}
    +
    { + e.preventDefault(); + const emoji = matches[0]; + if (emoji) { + onSelectEmoji(`:${emoji.shortcode}:`); + } + }} + > + +
    + +
    + {matches !== null ? ( +
      + {matches.map((emoji) => ( +
    • + { + onSelectEmoji(`:${emoji.shortcode}:`); + }} + showCode + /> +
    • + ))} +
    + ) : ( +
    + {uiState === 'error' && ( +
    +

    Error loading custom emojis

    +
    + )} + {uiState === 'default' && + Object.entries(customEmojisCatList).map( + ([category, emojis]) => + !!emojis?.length && ( + <> +
    + {{ + '--recent--': 'Recently used', + '--others--': 'Others', + }[category] || category} +
    + + + ), + )} +
    + )}
    ); } +const CustomEmojisList = memo(({ emojis, onSelect }) => { + const [max, setMax] = useState(CUSTOM_EMOJIS_COUNT); + const showMore = emojis.length > max; + return ( +
    + {emojis.slice(0, max).map((emoji) => ( + { + onSelect(`:${emoji.shortcode}:`); + }} + /> + ))} + {showMore && ( + + )} +
    + ); +}); + +const CustomEmojiButton = memo(({ emoji, onClick, showCode }) => { + const addEdges = (e) => { + // Add edge-left or edge-right class based on self position relative to scrollable parent + // If near left edge, add edge-left, if near right edge, add edge-right + const buffer = 88; + const parent = e.currentTarget.closest('main'); + if (parent) { + const rect = parent.getBoundingClientRect(); + const selfRect = e.currentTarget.getBoundingClientRect(); + const targetClassList = e.currentTarget.classList; + if (selfRect.left < rect.left + buffer) { + targetClassList.add('edge-left'); + targetClassList.remove('edge-right'); + } else if (selfRect.right > rect.right - buffer) { + targetClassList.add('edge-right'); + targetClassList.remove('edge-left'); + } else { + targetClassList.remove('edge-left', 'edge-right'); + } + } + }; + + return ( + + ); +}); + const GIFS_PER_PAGE = 20; function GIFPickerModal({ onClose = () => {}, onSelect = () => {} }) { const [uiState, setUIState] = useState('default'); diff --git a/src/components/status.css b/src/components/status.css index 247600c..96ed575 100644 --- a/src/components/status.css +++ b/src/components/status.css @@ -2386,8 +2386,8 @@ a.card:is(:hover, :focus):visited { max-width: 100%; height: 1.2em; vertical-align: text-bottom; - object-fit: cover; - object-position: left; + object-fit: contain; + /* object-position: left; */ } /* EDIT HISTORY */ From 5c9a47c31ed27e6a82847b330204f9926354048e Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Thu, 2 May 2024 00:14:48 +0800 Subject: [PATCH 9/9] Might as well re-use it for instances search --- src/pages/login.jsx | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/src/pages/login.jsx b/src/pages/login.jsx index 5d53cde..5a98ad3 100644 --- a/src/pages/login.jsx +++ b/src/pages/login.jsx @@ -1,5 +1,6 @@ import './login.css'; +import Fuse from 'fuse.js'; import { useEffect, useRef, useState } from 'preact/hooks'; import { useSearchParams } from 'react-router-dom'; @@ -27,12 +28,14 @@ function Login() { ); const [instancesList, setInstancesList] = useState([]); + const searcher = useRef(); useEffect(() => { (async () => { try { const res = await fetch(instancesListURL); const data = await res.json(); setInstancesList(data); + searcher.current = new Fuse(data); } catch (e) { // Silently fail console.error(e); @@ -90,21 +93,11 @@ function Login() { !/[\s\/\\@]/.test(cleanInstanceText); const instancesSuggestions = cleanInstanceText - ? instancesList - .filter((instance) => instance.includes(instanceText)) - .sort((a, b) => { - // Move text that starts with instanceText to the start - const aStartsWith = a - .toLowerCase() - .startsWith(instanceText.toLowerCase()); - const bStartsWith = b - .toLowerCase() - .startsWith(instanceText.toLowerCase()); - if (aStartsWith && !bStartsWith) return -1; - if (!aStartsWith && bStartsWith) return 1; - return 0; + ? searcher.current + ?.search(cleanInstanceText, { + limit: 10, }) - .slice(0, 10) + ?.map((match) => match.item) : []; const selectedInstanceText = instanceTextLooksLikeDomain