diff --git a/README.md b/README.md index 754ba9b5d..2f7db1648 100644 --- a/README.md +++ b/README.md @@ -273,6 +273,7 @@ These are self-hosted by other wonderful folks. - [phanpy.tilde.zone](https://phanpy.tilde.zone) by [@ben@tilde.zone](https://tilde.zone/@ben) - [phanpy.vmst.io](https://phanpy.vmst.io/) by [@vmstan@vmst.io](https://vmst.io/@vmstan) - [social.qrk.one](https://social.qrk.one) by [@kev@fosstodon.org](https://fosstodon.org/@kev) +- [phanpy.linuxusers.in](https://phanpy.linuxusers.in) by [@dharmik@linuxusers.in](https://linuxusers.in/dharmik) > Note: Add yours by creating a pull request. @@ -324,12 +325,14 @@ Costs involved in running and developing this web app: - llun (Thai) - lucasofchirst (Occitan, Portuguese, Portuguese, Brazilian) - LukeHong (Chinese Traditional) +- Mannivu (Italian) - marcin.kozinski (Polish) - mkljczkk (Polish) - mojosoeun (Korean) - moreal (Korean) - MrWillCom (Chinese Simplified) - nclm (French) +- nycterent (Lithuanian) - pazpi (Italian) - PPNplus (Thai) - punkrockgirl (Basque) diff --git a/package-lock.json b/package-lock.json index 040f1b3c6..b1bcfa704 100644 --- a/package-lock.json +++ b/package-lock.json @@ -58,6 +58,7 @@ "postcss": "~8.4.49", "postcss-dark-theme-class": "~1.3.0", "postcss-preset-env": "~10.1.1", + "prettier": "3.4.1", "sonda": "~0.6.1", "twitter-text": "~3.1.0", "vite": "~5.4.11", @@ -8406,17 +8407,15 @@ } }, "node_modules/prettier": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.0.tgz", - "integrity": "sha512-9Lmg8hTFZKG0Asr/kW9Bp8tJjRVluO8EJQVfY2T7FMw9T5jy4I/Uvx0Rca/XWf50QQ1/SS48+6IJWnrb+2yemA==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.1.tgz", + "integrity": "sha512-G+YdqtITVZmOJje6QkXQWzl3fSfMxFwm1tjTyo9exhkmWSqC4Yhd1+lug++IlR2mvRVAxEDDWYkQdeSztajqgg==", "dev": true, - "license": "MIT", - "peer": true, "bin": { - "prettier": "bin-prettier.js" + "prettier": "bin/prettier.cjs" }, "engines": { - "node": ">=10.13.0" + "node": ">=14" }, "funding": { "url": "https://github.com/prettier/prettier?sponsor=1" diff --git a/package.json b/package.json index 8ec05e59e..81917824c 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "postcss": "~8.4.49", "postcss-dark-theme-class": "~1.3.0", "postcss-preset-env": "~10.1.1", + "prettier": "3.4.1", "sonda": "~0.6.1", "twitter-text": "~3.1.0", "vite": "~5.4.11", diff --git a/src/app.jsx b/src/app.jsx index 8d95cc349..d8acfdb93 100644 --- a/src/app.jsx +++ b/src/app.jsx @@ -27,6 +27,7 @@ import SearchCommand from './components/search-command'; import Shortcuts from './components/shortcuts'; import NotFound from './pages/404'; import AccountStatuses from './pages/account-statuses'; +import AnnualReport from './pages/annual-report'; import Bookmarks from './pages/bookmarks'; import Catchup from './pages/catchup'; import Favourites from './pages/favourites'; @@ -98,39 +99,42 @@ window.__STATES_STATS__ = () => { // Experimental "garbage collection" for states // Every 15 minutes // Only posts for now -setInterval(() => { - if (!window.__IDLE__) return; - const { statuses, unfurledLinks, notifications } = states; - let keysCount = 0; - const { instance } = api(); - for (const key in statuses) { - if (!window.__IDLE__) break; - try { - const $post = document.querySelector( - `[data-state-post-id~="${key}"], [data-state-post-ids~="${key}"]`, - ); - const postInNotifications = notifications.some( - (n) => key === statusKey(n.status?.id, instance), - ); - if (!$post && !postInNotifications) { - delete states.statuses[key]; - delete states.statusQuotes[key]; - for (const link in unfurledLinks) { - const unfurled = unfurledLinks[link]; - const sKey = statusKey(unfurled.id, unfurled.instance); - if (sKey === key) { - delete states.unfurledLinks[link]; - break; +setInterval( + () => { + if (!window.__IDLE__) return; + const { statuses, unfurledLinks, notifications } = states; + let keysCount = 0; + const { instance } = api(); + for (const key in statuses) { + if (!window.__IDLE__) break; + try { + const $post = document.querySelector( + `[data-state-post-id~="${key}"], [data-state-post-ids~="${key}"]`, + ); + const postInNotifications = notifications.some( + (n) => key === statusKey(n.status?.id, instance), + ); + if (!$post && !postInNotifications) { + delete states.statuses[key]; + delete states.statusQuotes[key]; + for (const link in unfurledLinks) { + const unfurled = unfurledLinks[link]; + const sKey = statusKey(unfurled.id, unfurled.instance); + if (sKey === key) { + delete states.unfurledLinks[link]; + break; + } } + keysCount++; } - keysCount++; - } - } catch (e) {} - } - if (keysCount) { - console.info(`GC: Removed ${keysCount} keys`); - } -}, 15 * 60 * 1000); + } catch (e) {} + } + if (keysCount) { + console.info(`GC: Removed ${keysCount} keys`); + } + }, + 15 * 60 * 1000, +); // Preload icons // There's probably a better way to do this @@ -546,6 +550,7 @@ function SecondaryRoutes({ isLoggedIn }) { } /> } /> } /> + } /> )} } /> diff --git a/src/components/ICONS.jsx b/src/components/ICONS.jsx index cac8190bf..60fbb7c95 100644 --- a/src/components/ICONS.jsx +++ b/src/components/ICONS.jsx @@ -174,4 +174,5 @@ export const ICONS = { 'heart-break': () => import('@iconify-icons/mingcute/heart-crack-line'), 'user-x': () => import('@iconify-icons/mingcute/user-x-line'), minimize: () => import('@iconify-icons/mingcute/arrows-down-line'), + celebrate: () => import('@iconify-icons/mingcute/celebrate-line'), }; diff --git a/src/components/compose.css b/src/components/compose.css index d62de4701..554bf0b89 100644 --- a/src/components/compose.css +++ b/src/components/compose.css @@ -20,11 +20,15 @@ justify-content: space-between; gap: 8px; align-items: center; - padding: 16px; + padding: 8px; position: sticky; top: 0; z-index: 100; white-space: nowrap; + + @media (min-width: 480px) { + padding: 16px; + } } #compose-container .compose-top .account-block { text-align: start; @@ -110,10 +114,10 @@ } #compose-container form { - --form-padding-inline: 8px; - --form-padding-block: 0; + --form-spacing-inline: 4px; + --form-spacing-block: 0; /* border-radius: 16px; */ - padding: var(--form-padding-block) var(--form-padding-inline); + padding: var(--form-spacing-block) var(--form-spacing-inline); background-color: var(--bg-blur-color); /* background-image: linear-gradient(var(--bg-color) 85%, transparent); */ position: relative; @@ -121,6 +125,10 @@ --drop-shadow: 0 3px 6px -3px var(--drop-shadow-color); box-shadow: var(--drop-shadow); + @media (min-width: 480px) { + --form-spacing-inline: 8px; + } + @media (min-width: 40em) { border-radius: 16px; } @@ -153,8 +161,8 @@ display: flex; justify-content: space-between; align-items: center; - padding: 8px 0; - gap: 8px; + padding: var(--form-spacing-inline) 0; + gap: var(--form-spacing-inline); } #compose-container .toolbar.wrap { flex-wrap: wrap; @@ -181,6 +189,11 @@ white-space: nowrap; border: 2px solid transparent; vertical-align: middle; + + &.active { + filter: brightness(0.8); + background-color: var(--bg-color); + } } #compose-container .toolbar-button > * { vertical-align: middle; @@ -246,6 +259,39 @@ text-overflow: ellipsis; overflow: hidden; max-width: 100%; + min-width: 4ch; +} + +#compose-container .compose-footer { + .add-toolbar-button-group { + display: flex; + overflow: auto; + } + .add-sub-toolbar-button-group { + flex-grow: 1; + display: flex; + overflow: auto; + transition: 0.5s ease-in-out; + transition-property: opacity, width; + scrollbar-width: none; + padding-inline-end: 16px; + mask-image: linear-gradient( + var(--to-backward), + transparent 0, + black 16px, + black 100% + ); + + &::-webkit-scrollbar { + display: none; + } + + &[hidden] { + opacity: 0; + pointer-events: none; + width: 0; + } + } } #compose-container text-expander { @@ -516,6 +562,37 @@ color: var(--red-color); } +.compose-menu-add-media { + position: relative; + + .compose-menu-add-media-field { + position: absolute; + inset: 0; + opacity: 0; + cursor: inherit; + } +} + +.icon-gif { + display: inline-block !important; + min-width: 16px; + height: 16px; + font-size: 10px !important; + letter-spacing: -0.5px; + font-size-adjust: none; + overflow: hidden; + white-space: nowrap; + text-align: center; + line-height: 16px; + font-weight: bold; + text-rendering: optimizeSpeed; + + &:after { + display: block; + content: 'GIF'; + } +} + @media (display-mode: standalone) { /* No popping in standalone mode */ #compose-container .pop-button { @@ -525,8 +602,10 @@ #compose-container button[type='submit'] { border-radius: 8px; + @media (min-width: 480px) { padding-inline: 24px; + font-size: 125%; } } @@ -820,8 +899,8 @@ .compose-field-container { display: grid !important; - @media (width < 30em) { - margin-inline: calc(-1 * var(--form-padding-inline)); + @media (width < 480px) { + margin-inline: calc(-1 * var(--form-spacing-inline)); width: 100vw !important; max-width: 100vw; @@ -929,15 +1008,55 @@ } } +@keyframes jump-scare { + from { + opacity: 0.5; + transform: scale(0.25) translateX(80px); + } + to { + opacity: 1; + transform: scale(1) translateX(0); + } +} +@keyframes jump-scare-rtl { + from { + opacity: 0.5; + transform: scale(0.25) translateX(-80px); + } + to { + opacity: 1; + transform: scale(1) translateX(0); + } +} + +.add-button { + transform-origin: var(--forward) center; + background-color: var(--bg-blur-color) !important; + animation: jump-scare 0.2s ease-in-out both; + :dir(rtl) & { + animation-name: jump-scare-rtl; + } + + .icon { + transition: transform 0.3s ease-in-out; + } + &.active { + .icon { + transform: rotate(135deg); + } + } +} + .gif-picker-button { - span { + /* span { font-weight: bold; font-size: 11.5px; display: block; - } + line-height: 1; + } */ &:is(:hover, :focus) { - span { + .icon { animation: gif-shake 0.3s 3; } } diff --git a/src/components/compose.jsx b/src/components/compose.jsx index 4786ae9e2..775fd6370 100644 --- a/src/components/compose.jsx +++ b/src/components/compose.jsx @@ -19,6 +19,7 @@ import stringLength from 'string-length'; // import { detectAll } from 'tinyld/light'; import { uid } from 'uid/single'; import { useDebouncedCallback, useThrottledCallback } from 'use-debounce'; +import useResizeObserver from 'use-resize-observer'; import { useSnapshot } from 'valtio'; import poweredByGiphyURL from '../assets/powered-by-giphy.svg'; @@ -201,6 +202,13 @@ const LF = mem((locale) => new Intl.ListFormat(locale || undefined)); const CUSTOM_EMOJIS_COUNT = 100; +const ADD_LABELS = { + media: msg`Add media`, + customEmoji: msg`Add custom emoji`, + gif: msg`Add GIF`, + poll: msg`Add poll`, +}; + function Compose({ onClose, replyToStatus, @@ -209,7 +217,7 @@ function Compose({ standalone, hasOpener, }) { - const { i18n } = useLingui(); + const { i18n, _ } = useLingui(); const rtf = RTF(i18n.locale); const lf = LF(i18n.locale); @@ -732,6 +740,39 @@ function Compose({ states.composerState.minimized = true; }; + const gifPickerDisabled = + uiState === 'loading' || + (maxMediaAttachments !== undefined && + mediaAttachments.length >= maxMediaAttachments) || + !!poll; + + // If maxOptions is not defined or defined and is greater than 1, show poll button + const showPollButton = maxOptions == null || maxOptions > 1; + const pollButtonDisabled = + uiState === 'loading' || !!poll || !!mediaAttachments.length; + const onPollButtonClick = () => { + setPoll({ + options: ['', ''], + expiresIn: 24 * 60 * 60, // 1 day + multiple: false, + }); + }; + + const addSubToolbarRef = useRef(); + const [showAddButton, setShowAddButton] = useState(false); + useResizeObserver({ + ref: addSubToolbarRef, + box: 'border-box', + onResize: ({ width }) => { + // If scrollable, it's truncated + const { scrollWidth } = addSubToolbarRef.current; + const truncated = scrollWidth > width; + const overTruncated = width < 84; // roughly two buttons width + setShowAddButton(overTruncated || truncated); + addSubToolbarRef.current.hidden = overTruncated; + }, + }); + return (
@@ -1214,8 +1255,8 @@ function Compose({ replyToStatus ? t`Post your reply` : editStatus - ? t`Edit your post` - : t`What are you doing?` + ? t`Edit your post` + : t`What are you doing?` } required={mediaAttachments?.length === 0} disabled={uiState === 'loading'} @@ -1318,87 +1359,89 @@ function Compose({ }} /> )} -
- -
)} + {annualReportNotification && ( +
+
+ +
+
+ )}