diff --git a/packages/nicolium/.oxlintrc.json b/packages/nicolium/.oxlintrc.json index bff1bf1e5..8a6d13e26 100644 --- a/packages/nicolium/.oxlintrc.json +++ b/packages/nicolium/.oxlintrc.json @@ -62,6 +62,7 @@ "jsx-a11y/media-has-caption": "off", "jsx-a11y/mouse-events-have-key-events": "off", "jsx-a11y/no-autofocus": "off", + "jsx-a11y/no-noninteractive-tabindex": "warn", "jsx-a11y/no-static-element-interactions": "off", "jsx-a11y/prefer-tag-over-role": "off", diff --git a/packages/nicolium/package.json b/packages/nicolium/package.json index 2e57711fc..29bc9a8ac 100644 --- a/packages/nicolium/package.json +++ b/packages/nicolium/package.json @@ -56,15 +56,15 @@ "@reach/rect": "^0.18.0", "@reach/tabs": "^0.18.0", "@react-spring/web": "^10.0.3", - "@sentry/browser": "^10.45.0", - "@sentry/core": "^10.45.0", - "@sentry/react": "^10.45.0", + "@sentry/browser": "^10.47.0", + "@sentry/core": "^10.47.0", + "@sentry/react": "^10.47.0", "@tailwindcss/aspect-ratio": "^0.4.2", "@tailwindcss/forms": "^0.5.11", "@tailwindcss/typography": "^0.5.19", - "@tanstack/react-pacer": "^0.21.0", - "@tanstack/react-query": "^5.94.5", - "@tanstack/react-router": "^1.168.2", + "@tanstack/react-pacer": "^0.21.1", + "@tanstack/react-query": "^5.96.2", + "@tanstack/react-router": "^1.168.10", "@transfem-org/sfm-js": "^0.26.1", "@twemoji/svg": "^15.0.0", "@uidotdev/usehooks": "^2.4.1", @@ -83,7 +83,7 @@ "emoji-datasource": "15.0.1", "emoji-mart": "^5.6.0", "exifr": "^7.1.3", - "fast-average-color": "^9.5.0", + "fast-average-color": "^9.5.2", "fasttext.wasm.js": "^1.0.0", "fuzzysort": "^3.1.0", "html-react-parser": "^5.2.17", @@ -93,7 +93,7 @@ "lexical": "^0.42.0", "line-awesome": "^1.3.0", "localforage": "^1.10.0", - "lodash-es": "^4.17.23", + "lodash-es": "^4.18.1", "mutative": "^1.3.0", "object-to-formdata": "^4.5.1", "path-browserify": "^1.0.1", @@ -107,17 +107,17 @@ "react-datepicker": "^9.1.0", "react-dom": "^19.2.4", "react-hot-toast": "^2.6.0", - "react-inlinesvg": "^4.2.0", + "react-inlinesvg": "^4.3.0", "react-intl": "^8.1.3", "react-redux": "^9.2.0", "react-sparklines": "^1.7.0", "react-sticky-box": "^2.0.5", "react-swipeable-views": "^0.14.1", - "react-virtuoso": "^4.18.3", + "react-virtuoso": "^4.18.4", "redux": "^5.0.1", "redux-thunk": "^3.1.0", "reselect": "^5.1.1", - "sass-embedded": "^1.98.0", + "sass-embedded": "^1.99.0", "stringz": "^2.1.0", "tabbable": "^6.4.0", "use-mutative": "^1.3.1", @@ -127,12 +127,12 @@ }, "devDependencies": { "@formatjs/cli": "^6.13.1", - "@mkljczk/vite-tsgo-checker": "^0.0.2", - "@stylistic/stylelint-plugin": "^5.0.1", + "@mkljczk/vite-tsgo-checker": "^0.0.3", + "@stylistic/stylelint-plugin": "^5.1.0", "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.2", "@testing-library/user-event": "^14.6.1", - "@types/dom-chromium-ai": "^0.0.15", + "@types/dom-chromium-ai": "^0.0.16", "@types/leaflet": "^1.9.21", "@types/lodash": "^4.17.24", "@types/path-browserify": "^1.0.3", @@ -141,31 +141,31 @@ "@types/react-dom": "^19.2.3", "@types/react-sparklines": "^1.7.5", "@types/react-swipeable-views": "^0.13.6", - "@typescript/native-preview": "7.0.0-dev.20260322.1", + "@typescript/native-preview": "7.0.0-dev.20260407.1", "@vitejs/plugin-react": "^6.0.0", - "@vitest/coverage-v8": "4.1.0", + "@vitest/coverage-v8": "4.1.3", "eslint-plugin-formatjs": "^6.3.0", "globals": "^17.4.0", - "jsdom": "^29.0.1", - "oxfmt": "^0.41.0", - "oxlint": "^1.56.0", - "oxlint-tsgolint": "^0.17.1", + "jsdom": "^29.0.2", + "oxfmt": "^0.44.0", + "oxlint": "^1.59.0", + "oxlint-tsgolint": "^0.20.0", "rollup-plugin-bundle-stats": "^4.22.0", - "stylelint": "^17.5.0", + "stylelint": "^17.6.0", "stylelint-config-clean-order": "^8.0.1", "stylelint-config-standard-scss": "^17.0.0", "stylelint-order": "^8.0.0", "tailwindcss": "^3.4.19", "tslib": "^2.8.1", "type-fest": "^5.5.0", - "typescript": "5.9.3", - "vite": "^8.0.1", + "typescript": "6.0.2", + "vite": "^8.0.6", "vite-plugin-compile-time": "^0.4.6", "vite-plugin-html": "^3.2.2", "vite-plugin-pwa": "^1.2.0", "vite-plugin-require": "^1.2.14", "vite-plugin-static-copy": "^3.4.0", - "vitest": "^4.1.0" + "vitest": "^4.1.3" }, "lint-staged": { "*.{js,cjs,mjs,ts,tsx}": [ diff --git a/packages/nicolium/src/columns/timeline.tsx b/packages/nicolium/src/columns/timeline.tsx index 67413d568..d34ecd0f8 100644 --- a/packages/nicolium/src/columns/timeline.tsx +++ b/packages/nicolium/src/columns/timeline.tsx @@ -2,11 +2,11 @@ import iconArrowLineDown from '@phosphor-icons/core/regular/arrow-line-down.svg' import iconCaretDoubleDown from '@phosphor-icons/core/regular/caret-double-down.svg'; import iconCaretDoubleUp from '@phosphor-icons/core/regular/caret-double-up.svg'; import iconRepeat from '@phosphor-icons/core/regular/repeat.svg'; -import { Link } from '@tanstack/react-router'; import clsx from 'clsx'; import React, { useMemo, useRef, useState } from 'react'; import { defineMessages, FormattedList, FormattedMessage, useIntl } from 'react-intl'; +import { AccountLink } from '@/components/accounts/account-link'; import HoverAccountWrapper from '@/components/accounts/hover-account-wrapper'; import PlaceholderStatus from '@/components/placeholders/placeholder-status'; import PullToRefresh from '@/components/pull-to-refresh'; @@ -229,14 +229,12 @@ const TimelineStatusInfo: React.FC = ({ (account) => !!account && ( - - + ), ); diff --git a/packages/nicolium/src/components/accounts/account-link.tsx b/packages/nicolium/src/components/accounts/account-link.tsx new file mode 100644 index 000000000..a9616d7dc --- /dev/null +++ b/packages/nicolium/src/components/accounts/account-link.tsx @@ -0,0 +1,36 @@ +import { Link } from '@tanstack/react-router'; +import React from 'react'; + +import { useFrontendConfig } from '@/hooks/use-frontend-config'; +import { useLoggedIn } from '@/hooks/use-logged-in'; + +import type { Account, Mention } from 'pl-api'; + +interface IAccountLink extends React.AnchorHTMLAttributes { + account: Pick | Mention; +} + +const AccountLink: React.FC = ({ account, ...props }) => { + const { isLoggedIn } = useLoggedIn(); + const { allowDisplayingRemoteNoLogin } = useFrontendConfig(); + + const local = 'local' in account ? account.local : !account.acct.includes('@'); + + if (!isLoggedIn && !local && !allowDisplayingRemoteNoLogin) { + return ( + + ); + } + + return ( + + ); +}; + +export { AccountLink }; diff --git a/packages/nicolium/src/components/accounts/account.tsx b/packages/nicolium/src/components/accounts/account.tsx index 241febafc..2caa6b9a7 100644 --- a/packages/nicolium/src/components/accounts/account.tsx +++ b/packages/nicolium/src/components/accounts/account.tsx @@ -168,6 +168,9 @@ const Account = ({ frontendConfig.patron.enabled && account.local ? account.url : undefined, ); const { disableUserProvidedMedia } = useSettings(); + const { allowDisplayingRemoteNoLogin } = useFrontendConfig(); + + const withExternalLink = !allowDisplayingRemoteNoLogin && !account.local; const handleAction = () => { onActionClick!(account); @@ -240,16 +243,26 @@ const Account = ({ if (withDate) timestamp = account.created_at; - const LinkEl: React.ElementType = withLinkToProfile ? Link : 'div'; + const LinkEl: React.ElementType = withLinkToProfile ? (withExternalLink ? 'a' : Link) : 'div'; const linkProps = withLinkToProfile - ? { - to: '/@{$username}', - params: { username: account.acct }, - title: account.acct, - onClick: (event: React.MouseEvent) => { - event.stopPropagation(); - }, - } + ? withExternalLink + ? { + href: account.url, + target: '_blank', + rel: 'noopener noreferrer', + title: account.acct, + onClick: (event: React.MouseEvent) => { + event.stopPropagation(); + }, + } + : { + to: '/@{$username}', + params: { username: account.acct }, + title: account.acct, + onClick: (event: React.MouseEvent) => { + event.stopPropagation(); + }, + } : {}; if (disabled) diff --git a/packages/nicolium/src/components/accounts/mention.tsx b/packages/nicolium/src/components/accounts/mention.tsx index 13d326cee..b68ba1e55 100644 --- a/packages/nicolium/src/components/accounts/mention.tsx +++ b/packages/nicolium/src/components/accounts/mention.tsx @@ -1,17 +1,18 @@ import React from 'react'; -import { Link } from '@/components/link'; import Tooltip from '@/components/ui/tooltip'; +import { AccountLink } from './account-link'; + import type { Mention as MentionEntity } from 'pl-api'; interface IMention { - mention: Pick; + mention: MentionEntity; disabled?: boolean; } /** Mention for display in the composer. */ -const Mention: React.FC = ({ mention: { acct, username }, disabled }) => { +const Mention: React.FC = ({ mention, disabled }) => { const handleClick: React.MouseEventHandler = (e) => { if (disabled) { e.preventDefault(); @@ -20,10 +21,10 @@ const Mention: React.FC = ({ mention: { acct, username }, disabled }) }; return ( - - - @{username} - + + + @{mention.username} + ); }; diff --git a/packages/nicolium/src/components/accounts/profile-familiar-followers.tsx b/packages/nicolium/src/components/accounts/profile-familiar-followers.tsx index 4f94b28b2..fb2e66869 100644 --- a/packages/nicolium/src/components/accounts/profile-familiar-followers.tsx +++ b/packages/nicolium/src/components/accounts/profile-familiar-followers.tsx @@ -1,4 +1,3 @@ -import { Link } from '@tanstack/react-router'; import React from 'react'; import { FormattedList, FormattedMessage } from 'react-intl'; @@ -11,6 +10,8 @@ import { useAccount } from '@/queries/accounts/use-account'; import { useFamiliarFollowers } from '@/queries/accounts/use-familiar-followers'; import { useModalsActions } from '@/stores/modals'; +import { AccountLink } from './account-link'; + import type { Account } from 'pl-api'; interface IFamiliarFollowerLink { @@ -23,12 +24,7 @@ const FamiliarFollowerLink: React.FC = ({ id }) => { if (!account) return null; return ( - +
@@ -38,7 +34,7 @@ const FamiliarFollowerLink: React.FC = ({ id }) => { {account.verified && }
- +
); }; diff --git a/packages/nicolium/src/components/accounts/profile-stats.tsx b/packages/nicolium/src/components/accounts/profile-stats.tsx index d524fb55a..4085ac03f 100644 --- a/packages/nicolium/src/components/accounts/profile-stats.tsx +++ b/packages/nicolium/src/components/accounts/profile-stats.tsx @@ -2,6 +2,7 @@ import { Link } from '@tanstack/react-router'; import React from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; +import { useCurrentAccount } from '@/contexts/current-account-context'; import { useSettings } from '@/stores/settings'; import { shortNumberFormat } from '@/utils/numbers'; @@ -27,7 +28,16 @@ interface IProfileStats { account: | Pick< Account, - 'acct' | 'followers_count' | 'following_count' | 'statuses_count' | 'subscribers_count' + | 'acct' + | 'followers_count' + | 'following_count' + | 'hide_followers' + | 'hide_followers_count' + | 'hide_follows' + | 'hide_follows_count' + | 'id' + | 'statuses_count' + | 'subscribers_count' > | undefined; onClickHandler?: React.MouseEventHandler; @@ -37,11 +47,20 @@ interface IProfileStats { const ProfileStats: React.FC = ({ account, onClickHandler }) => { const intl = useIntl(); const { demetricator } = useSettings(); + const me = useCurrentAccount(); + + const ownAccount = account?.id === me; if (!account) { return null; } + const showFollowers = ownAccount || !account.hide_followers_count || !account.hide_followers; + const showFollowing = ownAccount || !account.hide_follows_count || !account.hide_follows; + + const FollowersComponent = !account.hide_followers || ownAccount ? Link : 'div'; + const FollowingComponent = !account.hide_follows || ownAccount ? Link : 'div'; + return (
{!demetricator && ( @@ -51,27 +70,43 @@ const ProfileStats: React.FC = ({ account, onClickHandler }) => {
)} - - {!demetricator && {shortNumberFormat(account.followers_count)}} + {showFollowers ? ( + + {!demetricator && !(account.hide_followers_count && !ownAccount) && ( + {shortNumberFormat(account.followers_count)} + )} - - + + + ) : ( +
+ +
+ )} - - {!demetricator && {shortNumberFormat(account.following_count)}} + {showFollowing ? ( + + {!demetricator && !(account.hide_follows_count && !ownAccount) && ( + {shortNumberFormat(account.following_count)} + )} - - + + + ) : ( +
+ +
+ )} {account.subscribers_count > 0 && ( = ({ value, onChange, required }) }; return ( -
+