Merge branch 'develop' of https://codeberg.org/mkljczk/pl-fe into develop

This commit is contained in:
2026-04-02 01:40:24 +00:00
31 changed files with 465 additions and 190 deletions

View File

@@ -0,0 +1,44 @@
{
"name": "Nicolium",
"dockerComposeFile": "docker-compose.yml",
"service": "app",
"workspaceFolder": "/workspaces/nicolium",
"features": {
"ghcr.io/devcontainers/features/node:1": {
"version": "20",
"nodeGypDependencies": true
}
},
"remoteUser": "vscode",
"updateRemoteUserUID": true,
"forwardPorts": [7312],
"portsAttributes": {
"7312": {
"label": "Nicolium",
"onAutoForward": "openBrowser"
}
},
"postCreateCommand": "bash .devcontainer/post-create.sh",
"customizations": {
"vscode": {
"extensions": [
"dbaeumer.vscode-eslint",
"oxc.oxc-vscode",
"stylelint.vscode-stylelint",
"wix.vscode-import-cost"
]
}
},
"containerEnv": {
"WAYLAND_DISPLAY": ""
},
"remoteEnv": {
"WAYLAND_DISPLAY": ""
}
}

View File

@@ -0,0 +1,10 @@
version: "3.8"
services:
app:
image: mcr.microsoft.com/devcontainers/base:ubuntu
command: sleep infinity
volumes:
- ..:/workspaces/nicolium:Z
ports:
- "127.0.0.1:7312:7312"

View File

@@ -0,0 +1,24 @@
#!/usr/bin/env bash
set -euo pipefail
corepack enable
mkdir -p ~/.local/bin
platform="$(uname -s)-$(uname -m)"
formatjs_url=""
case "$platform" in
Linux-x86_64)
formatjs_url="https://github.com/formatjs/formatjs/releases/download/formatjs_cli_v1.1.0/formatjs_cli-linux-x64"
;;
Darwin-arm64)
formatjs_url="https://github.com/formatjs/formatjs/releases/download/formatjs_cli_v1.1.0/formatjs_cli-darwin-arm64"
;;
esac
if [[ -n "$formatjs_url" ]]; then
curl -fsSL "$formatjs_url" -o ~/.local/bin/formatjs
chmod +x ~/.local/bin/formatjs
fi
pnpm install --ignore-scripts

2
.gitignore vendored
View File

@@ -1,4 +1,6 @@
node_modules
/node_modules/
yarn-error.log*
coverage/
package-lock.json
.pnpm-store

View File

@@ -1,6 +1,8 @@
{
"recommendations": [
"dbaeumer.vscode-eslint",
"ms-vscode-remote.remote-containers",
"oxc.oxc-vscode",
"stylelint.vscode-stylelint",
"wix.vscode-import-cost"
]

View File

@@ -209,10 +209,16 @@ const TimelineGap: React.FC<ITimelineGap> = ({ gap, onFillGap, firstEntry }) =>
interface ITimelineStatusInfo {
status: SelectedStatus;
rebloggedBy: Array<string>;
reblogVisibility?: string;
timelineId: string;
}
const TimelineStatusInfo: React.FC<ITimelineStatusInfo> = ({ status, rebloggedBy, timelineId }) => {
const TimelineStatusInfo: React.FC<ITimelineStatusInfo> = ({
status,
rebloggedBy,
reblogVisibility,
timelineId,
}) => {
const features = useFeatures();
const isReblogged = rebloggedBy.length > 0;
@@ -256,19 +262,19 @@ const TimelineStatusInfo: React.FC<ITimelineStatusInfo> = ({ status, rebloggedBy
avatarSize={42}
icon={<Icon src={iconRepeat} className='size-4 text-green-600' aria-hidden />}
text={
// status.visibility === 'private' ? (
// <FormattedMessage
// id='status.reblogged_by_private'
// defaultMessage='{name} reposted to followers'
// values={values}
// />
// ) : (
<FormattedMessage
id='status.reblogged_by'
defaultMessage='{name} reposted'
values={values}
/>
// )
reblogVisibility === 'private' ? (
<FormattedMessage
id='status.reblogged_by_private'
defaultMessage='{name} reposted to followers'
values={values}
/>
) : (
<FormattedMessage
id='status.reblogged_by'
defaultMessage='{name} reposted'
values={values}
/>
)
}
/>
);
@@ -281,6 +287,7 @@ const TimelineStatusInfo: React.FC<ITimelineStatusInfo> = ({ status, rebloggedBy
interface ITimelineStatus {
id: string;
rebloggedBy: Array<string>;
reblogVisibility?: string;
timelineId: string;
contextType?: FilterContextType;
isConnectedTop?: boolean;
@@ -341,6 +348,7 @@ const TimelineStatus: React.FC<ITimelineStatus> = (props): React.JSX.Element =>
<TimelineStatusInfo
status={statusQuery.data!}
rebloggedBy={props.rebloggedBy}
reblogVisibility={props.reblogVisibility}
timelineId={props.timelineId}
/>
)}
@@ -440,6 +448,7 @@ const Timeline: React.FC<ITimeline> = ({
onMoveUp={() => handleMoveUp(index)}
onMoveDown={() => handleMoveDown(index)}
rebloggedBy={entry.rebloggedBy}
reblogVisibility={entry.reblogVisibility}
timelineId={timelineId}
// showGroup={showGroup}
/>

View File

@@ -2,7 +2,6 @@ import { Link } from '@tanstack/react-router';
import React from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import Text from '@/components/ui/text';
import { useSettings } from '@/stores/settings';
import { shortNumberFormat } from '@/utils/numbers';
@@ -44,18 +43,11 @@ const ProfileStats: React.FC<IProfileStats> = ({ account, onClickHandler }) => {
}
return (
<div className='flex flex-wrap items-center gap-x-3'>
<div className='⁂-account-stats'>
{!demetricator && (
<div
className='flex items-center gap-1'
title={intl.formatMessage(messages.statusesCount, { count: account.statuses_count })}
>
<Text theme='primary' weight='bold' size='sm'>
{shortNumberFormat(account.statuses_count)}
</Text>
<Text weight='bold' size='sm'>
<FormattedMessage id='account.statuses' defaultMessage='Statuses' />
</Text>
<div title={intl.formatMessage(messages.statusesCount, { count: account.statuses_count })}>
<strong>{shortNumberFormat(account.statuses_count)}</strong>
<FormattedMessage id='account.statuses' defaultMessage='Statuses' />
</div>
)}
@@ -63,44 +55,22 @@ const ProfileStats: React.FC<IProfileStats> = ({ account, onClickHandler }) => {
to='/@{$username}/followers'
params={{ username: account.acct }}
onClick={onClickHandler}
title={intl.formatNumber(account.followers_count)}
className='hover:underline'
title={intl.formatMessage(messages.followersCount, { count: account.followers_count })}
>
<div
className='flex items-center gap-1'
title={intl.formatMessage(messages.followersCount, { count: account.followers_count })}
>
{!demetricator && (
<Text theme='primary' weight='bold' size='sm'>
{shortNumberFormat(account.followers_count)}
</Text>
)}
<Text weight='bold' size='sm'>
<FormattedMessage id='account.followers' defaultMessage='Followers' />
</Text>
</div>
{!demetricator && <strong>{shortNumberFormat(account.followers_count)}</strong>}
<FormattedMessage id='account.followers' defaultMessage='Followers' />
</Link>
<Link
to='/@{$username}/following'
params={{ username: account.acct }}
onClick={onClickHandler}
title={intl.formatNumber(account.following_count)}
className='hover:underline'
title={intl.formatMessage(messages.followingCount, { count: account.following_count })}
>
<div
className='flex items-center gap-1'
title={intl.formatMessage(messages.followingCount, { count: account.following_count })}
>
{!demetricator && (
<Text theme='primary' weight='bold' size='sm'>
{shortNumberFormat(account.following_count)}
</Text>
)}
<Text weight='bold' size='sm'>
<FormattedMessage id='account.follows' defaultMessage='Following' />
</Text>
</div>
{!demetricator && <strong>{shortNumberFormat(account.following_count)}</strong>}
<FormattedMessage id='account.follows' defaultMessage='Following' />
</Link>
{account.subscribers_count > 0 && (
@@ -108,24 +78,13 @@ const ProfileStats: React.FC<IProfileStats> = ({ account, onClickHandler }) => {
to='/@{$username}/subscribers'
params={{ username: account.acct }}
onClick={onClickHandler}
title={intl.formatNumber(account.subscribers_count)}
className='hover:underline'
title={intl.formatMessage(messages.subscribersCount, {
count: account.subscribers_count,
})}
>
<div
className='flex items-center gap-1'
title={intl.formatMessage(messages.subscribersCount, {
count: account.subscribers_count,
})}
>
{!demetricator && (
<Text theme='primary' weight='bold' size='sm'>
{shortNumberFormat(account.subscribers_count)}
</Text>
)}
<Text weight='bold' size='sm'>
<FormattedMessage id='account.subscribers' defaultMessage='Subscribers' />
</Text>
</div>
{!demetricator && <strong>{shortNumberFormat(account.subscribers_count)}</strong>}
<FormattedMessage id='account.subscribers' defaultMessage='Subscribers' />
</Link>
)}
</div>

View File

@@ -30,7 +30,7 @@ const SidebarNavigationLink: React.FC<ISidebarNavigationLink> = React.memo(
const LinkComponent = (to === undefined ? 'button' : Link) as typeof Link;
const isActive = matchRoute({ to }) !== false;
const isActive = matchRoute({ to, ...rest }) !== false;
const handleClick: React.EventHandler<React.MouseEvent> = (e) => {
if (onClick) {
@@ -43,7 +43,7 @@ const SidebarNavigationLink: React.FC<ISidebarNavigationLink> = React.memo(
return (
<li>
<LinkComponent
activeOptions={{ exact: true }}
activeOptions={{ exact: true, includeSearch: false }}
activeProps={{ className: '⁂-sidebar-navigation-link--active' }}
to={to}
ref={ref}

View File

@@ -105,7 +105,6 @@ const StatusList: React.FC<IStatusList> = ({
showGroup={showGroup}
variant='slim'
fromBookmarks={other.scrollKey === 'bookmarked_statuses'}
fromHomeTimeline={timelineId === 'home'}
/>
);

View File

@@ -15,7 +15,6 @@ import Text from '@/components/ui/text';
import Emojify from '@/features/emoji/emojify';
import StatusTypeIcon from '@/features/status/components/status-type-icon';
import { Hotkeys } from '@/features/ui/components/hotkeys';
import { useFeatures } from '@/hooks/use-features';
import { useGroupQuery } from '@/queries/groups/use-group';
import { useFollowedTags } from '@/queries/hashtags/use-followed-tags';
import { useStatus, type SelectedStatus } from '@/queries/statuses/use-status';
@@ -175,7 +174,6 @@ interface IStatus {
showGroup?: boolean;
showInfo?: boolean;
fromBookmarks?: boolean;
fromHomeTimeline?: boolean;
className?: string;
}
@@ -196,7 +194,6 @@ const Status: React.FC<IStatus> = React.memo((props) => {
showGroup = true,
showInfo = true,
fromBookmarks = false,
fromHomeTimeline = false,
className,
contextType,
} = props;
@@ -204,7 +201,6 @@ const Status: React.FC<IStatus> = React.memo((props) => {
const intl = useIntl();
const navigate = useNavigate();
const router = useRouter();
const features = useFeatures();
const { toggleStatusesMediaHidden, unfilterStatus } = useStatusMetaActions();
const { deleted, showFiltered } = useStatusMeta(status.id);
@@ -479,12 +475,6 @@ const Status: React.FC<IStatus> = React.memo((props) => {
}
/>
);
} else if (fromHomeTimeline) {
return (
features.followHashtags && (
<StatusFollowedTagInfo className='-mb-1' status={actualStatus} avatarSize={avatarSize} />
)
);
}
}, [status.account, group?.id]);

View File

@@ -309,6 +309,7 @@ const EditEvent: React.FC<IEditEvent> = ({ statusId }) => {
<ContentTypeButton composeId={composeId} />
<ComposeEditor
key={String(isDisabled)}
className='⁂-edit-event__editor'
placeholderClassName='⁂-compose-form__editor__placeholder'
composeId={composeId}
placeholder={intl.formatMessage(messages.eventDescriptionPlaceholder)}

View File

@@ -1,4 +1,3 @@
import clsx from 'clsx';
import React from 'react';
import IconButton from '@/components/ui/icon-button';
@@ -20,11 +19,7 @@ const ComposeFormButton: React.FC<IComposeFormButton> = ({
}) => (
<div>
<IconButton
className={clsx({
'text-gray-600 hover:text-gray-700 dark:hover:text-gray-500': !active,
'text-primary-500 hover:text-primary-600 dark:text-primary-500 dark:hover:text-primary-400':
active,
})}
className='⁂-compose-form__button'
src={icon}
title={title}
aria-label={title}

View File

@@ -109,14 +109,14 @@ const ComposeButton: React.FC<IComposeButton> = ({
const intl = useIntl();
return (
<div className='⁂-compose-form__button__container'>
<button {...props} disabled={disabled} className='⁂-compose-form__button'>
<div className='⁂-compose-form__send-button__container'>
<button {...props} disabled={disabled} className='⁂-compose-form__send-button'>
{icon ? <Icon src={icon} /> : null}
<span>{text}</span>
</button>
<DropdownMenu items={actionsMenu} placement='bottom' disabled={disabled}>
<button
className='⁂-compose-form__button__actions'
className='⁂-compose-form__send-button__actions'
title={intl.formatMessage(messages.more)}
>
<SvgIcon src={iconCaretDown} aria-hidden />

View File

@@ -193,20 +193,15 @@ const ComposeEditor = React.forwardRef<LexicalEditor, IComposeEditor>(
return (
<LexicalComposer key={isWysiwyg ? 'wysiwyg' : 'no-wysiwyg'} initialConfig={initialConfig}>
<div className={clsx('lexical relative', className)} data-markup>
<div className={className} data-markup>
<RichTextPlugin
contentEditable={
<div onFocus={onFocus} onPaste={handlePaste} ref={onRef}>
<ContentEditable
tabIndex={0}
className={clsx(
'relative z-10 text-[1rem] outline-none transition-[min-height] motion-reduce:transition-none',
editableClassName,
{
'min-h-[39px]': condensed,
'min-h-[99px]': !condensed,
},
)}
className={clsx('⁂-compose-form__editor__editable', editableClassName, {
'⁂-compose-form__editor__editable--condensed': condensed,
})}
lang={language ?? undefined}
data-compose-id={composeId}
aria-label={textareaPlaceholder}

View File

@@ -1,6 +1,5 @@
import { useFloating, shift, flip, autoUpdate } from '@floating-ui/react';
import iconSmiley from '@phosphor-icons/core/regular/smiley.svg';
import clsx from 'clsx';
import React, { useMemo, useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
@@ -22,11 +21,9 @@ interface IEmojiPickerDropdownContainer extends Pick<
'onPickEmoji' | 'condensed' | 'withCustom'
> {
children?: React.JSX.Element;
theme?: 'default' | 'inverse';
}
const EmojiPickerDropdownContainer: React.FC<IEmojiPickerDropdownContainer> = ({
theme = 'default',
children,
...props
}) => {
@@ -87,12 +84,7 @@ const EmojiPickerDropdownContainer: React.FC<IEmojiPickerDropdownContainer> = ({
{clonedChildren ?? (
<IconButton
theme='transparent'
className={clsx('emoji-picker-dropdown -m-1 p-2', {
'bg-transparent text-gray-600 hover:bg-primary-100 hover:text-gray-800 black:hover:bg-gray-800 dark:hover:bg-primary-800 dark:hover:text-white':
theme === 'default',
'bg-transparent text-white/80 hover:text-white dark:bg-transparent':
theme === 'inverse',
})}
className='emoji-picker-dropdown -m-1 bg-transparent p-2 text-gray-600 hover:bg-primary-100 hover:text-gray-800 black:hover:bg-gray-800 dark:hover:bg-primary-800 dark:hover:text-white'
ref={refs.setReference}
src={iconSmiley}
title={title}

View File

@@ -77,7 +77,7 @@ const GroupTimelinePage: React.FC = () => {
/>
}
emptyMessageIcon={iconChatCenteredText}
// showGroup={falsse}
// showGroup={false}
/>
</div>
);

View File

@@ -15,6 +15,7 @@ type TimelineEntry =
accountId: string;
rebloggedBy: Array<string>;
reblogIds: Array<string>;
reblogVisibility?: string;
isConnectedTop?: boolean;
isConnectedBottom?: boolean;
isReply: boolean;
@@ -125,6 +126,9 @@ const processPage = (statuses: Array<Status>): Array<TimelineEntry> => {
if (!existingEntry.rebloggedBy.includes(status.account.id)) {
existingEntry.rebloggedBy.push(status.account.id);
existingEntry.reblogIds.push(status.id);
if (existingEntry.reblogVisibility !== status.visibility) {
existingEntry.reblogVisibility = undefined;
}
}
} else {
timelinePage.push({
@@ -134,6 +138,7 @@ const processPage = (statuses: Array<Status>): Array<TimelineEntry> => {
accountId: status.reblog.account.id,
rebloggedBy: [status.account.id],
reblogIds: [status.id],
reblogVisibility: status.visibility,
isConnectedTop,
isReply: status.reblog.in_reply_to_id !== null,
isReblog: true,

View File

@@ -1,11 +1,36 @@
.empty-column-indicator {
@apply bg-primary-50 dark:bg-gray-700 text-gray-900 dark:text-gray-100 text-center p-10 flex flex-1 items-center justify-center min-h-[160px] rounded-lg;
display: flex;
flex: 1 1 0%;
align-items: center;
justify-content: center;
min-height: 10rem;
border-radius: 0.5rem;
color: rgb(var(--color-gray-900));
text-align: center;
background-color: rgb(var(--color-primary-900));
.dark {
color: rgb(var(--color-gray-100));
background-color: rgb(var(--color-primary-700));
}
& > span {
@apply max-w-[400px];
max-width: 400px;
}
a {
@apply text-primary-600 dark:text-primary-400 no-underline hover:underline;
color: rgb(var(--color-primary-600));
text-decoration: none;
.dark & {
color: rgb(var(--color-primary-400));
}
&:hover {
text-decoration: underline;
}
}
}

View File

@@ -1,13 +1,11 @@
.compose-form__upload-thumbnail {
&.video {
@apply bg-cover;
background-image: url('../assets/images/video-placeholder.png');
background-size: cover;
}
&.audio {
@apply bg-cover;
background-image: url('../assets/images/audio-placeholder.png');
background-size: cover;
}
}

View File

@@ -58,7 +58,7 @@
}
.react-datepicker__navigation--next {
@apply right-4;
right: 1rem;
&--with-time:not(.react-datepicker__navigation--next--with-today-button) {
right: 100px;
@@ -76,12 +76,13 @@
}
.react-datepicker__header__dropdown {
@apply py-4;
padding: 1rem 0;
}
.react-datepicker__day-names,
.react-datepicker__week {
@apply flex justify-between;
display: flex;
justify-content: space-between;
}
.react-datepicker__time {

View File

@@ -1,8 +1,18 @@
.svg-icon {
@apply h-4 w-4 flex items-center justify-center transition duration-200;
display: flex;
align-items: center;
justify-content: center;
width: 1rem;
height: 1rem;
transition: all 200ms var(--ease-default);
svg {
width: 100%;
// Apparently this won't skew the image as long as it has a viewbox
@apply h-full w-full transition duration-200;
height: 100%;
transition: all 200ms var(--ease-default);
}
}

View File

@@ -1,18 +1,23 @@
.-media-modal {
.audio-player.detailed,
.extended-video-player {
@apply flex items-center justify-center;
display: flex;
align-items: center;
justify-content: center;
}
.audio-player {
@apply max-w-[800px] max-h-[600px];
max-width: 50rem;
max-height: 36rem;
}
.extended-video-player {
@apply w-full h-full;
width: 100%;
height: 100%;
video {
@apply max-w-full max-h-[80%];
max-width: 100%;
max-height: 80%;
}
}
}

View File

@@ -40,17 +40,30 @@
flex: 0 1 auto;
.player-button {
@apply block outline-0 bg-transparent text-base border-0 text-black dark:text-white rounded-full;
display: block;
flex: 0 0 auto;
padding: 4px;
border-width: 0;
border-radius: 9999px;
font-size: 0.9375rem;
color: black;
background-color: transparent;
outline-width: 0;
.dark {
color: white;
}
&:hover {
@apply dark:bg-primary-200/20 bg-primary-700/20 rounded-full;
}
.svg-icon {
@apply w-5 h-5;
width: 1.25rem;
height: 1.25rem;
}
}
}

View File

@@ -9,7 +9,19 @@
}
.loading-indicator {
@apply text-gray-50 dark:text-gray-800 text-xs uppercase flex flex-col items-center justify-center overflow-visible;
overflow: visible;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: 0.75rem;
color: rgb(var(--color-gray-50));
text-transform: uppercase;
.dark {
color: rgb(var(--color-gray-800));
}
}
.loading-indicator__container {
@@ -19,10 +31,22 @@
}
.loading-indicator__figure {
@apply absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-12 h-12 rounded-full bg-transparent dark:bg-transparent border-[#e5e7eb] dark:border-gray-800;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border: 0 solid;
width: 3rem;
height: 3rem;
border: 0 solid #e5e7eb;
border-width: 6px;
border-radius: 9999px;
background-color: transparent;
.dark {
border-color: rgb(var(--color-gray-800));
}
}
.no-reduce-motion .loading-indicator__figure {
@@ -30,29 +54,45 @@
}
.loading-indicator-wrapper .loading-indicator {
@apply h-screen w-screen items-center;
align-items: center;
width: 100vw;
height: 100vh;
&__figure {
@apply border-gray-200 dark:border-gray-800;
border-color: rgb(var(--color-gray-200));
.dark & {
border-color: rgb(var(--color-gray-800));
}
}
}
@keyframes loader-figure {
0% {
@apply bg-yellow-200 w-0 h-0;
width: 0;
height: 0;
background: var(--hex-yellow-200);
}
29% {
@apply bg-gray-200;
background: rgb(var(--color-gray-200));
}
30% {
@apply w-12 h-12 bg-transparent opacity-100;
width: 3rem;
height: 3rem;
border-width: 6px;
opacity: 1;
background: transparent;
}
100% {
@apply w-12 h-12 border-0 opacity-0 bg-transparent;
width: 3rem;
height: 3rem;
border-width: 0;
opacity: 0;
background: transparent;
}
}

View File

@@ -2,61 +2,77 @@ $vertical-lr-langs: mn-mong, mnmong;
[data-markup],
[data-lexical-editor] {
@apply whitespace-pre-wrap;
white-space: pre-wrap;
h1 {
@apply text-3xl font-semibold;
font-size: 1.875rem;
font-weight: 600;
line-height: 2.25rem;
}
h2 {
@apply text-2xl font-semibold;
font-size: 1.5rem;
font-weight: 600;
line-height: 2rem;
}
h3 {
@apply text-xl font-black;
font-size: 1.25rem;
font-weight: 900;
line-height: 1.75rem;
}
hr {
&:not(:last-child) {
@apply mb-4;
margin-bottom: 1rem;
}
}
p {
@apply whitespace-pre-wrap;
white-space: pre-wrap;
&:not(:last-child) {
@apply mb-4;
margin-bottom: 1rem;
}
}
a {
@apply text-primary-600 dark:text-primary-400 hover:underline;
color: rgb(var(--color-primary-600));
.dark {
color: rgb(var(--color-primary-400));
}
&:hover {
text-decoration: underline;
}
}
strong {
@apply font-bold;
font-weight: 700;
}
em {
@apply italic;
font-style: italic
}
ul,
ol {
@apply pl-10;
padding-left: 2.5rem;
&:not(:last-child) {
@apply mb-4;
margin-bottom: 1rem;
}
}
ul {
@apply list-disc list-outside;
list-style-position: outside;
list-style-type: disc;
}
ol {
@apply list-decimal list-outside;
list-style-position: outside;
list-style-type: decimal;
}
li {
@@ -69,70 +85,119 @@ $vertical-lr-langs: mn-mong, mnmong;
}
blockquote {
@apply py-1 pl-4 border-l-4 border-solid border-gray-400 text-gray-500 dark:text-gray-400;
padding: 0.25rem 0 0.25rem 1rem;
border-left: 4px solid rgb(var(--color-gray-400));
color: rgb(var(--color-gray-500));
.dark {
color: rgb(var(--color-gray-400));
}
&:not(:last-child) {
@apply mb-4;
margin-bottom: 1rem;
}
}
table {
@apply table-auto w-full bg-gray-200 dark:bg-gray-900 my-4 rounded-md;
table-layout: auto;
width: 100%;
margin: 1rem 0;
border-radius: 0.375rem;
background-color: rgb(var(--color-gray-200));
.dark {
background-color: rgb(var(--color-gray-900));
}
}
table th,
table td {
@apply text-center px-2;
align-items: center;
padding: 0 0.5rem;
}
table th {
@apply border-b-2 border-gray-600;
border: 2px solid rgb(var(--color-gray-600));
}
code,
pre {
@apply cursor-text font-mono;
cursor: text;
font-family: "Roboto Mono", ui-monospace, monospace;
}
p > code,
pre {
@apply bg-gray-100 dark:bg-primary-800;
background: rgb(var(--color-gray-100));
.dark {
background: rgb(var(--color-primary-800));
}
}
/* Inline code */
p > code {
@apply py-0.5 px-1 rounded-sm;
padding: 0.125rem 0.25rem;
border-radius: 0.125rem;
}
/* Code block */
pre {
@apply py-2 px-3 leading-6 overflow-x-auto rounded-md break-all;
overflow-x: auto;
padding: 0.5rem 0.75rem;
border-radius: 0.375rem;
line-height: 1.5rem;
word-break: break-all;
&:not(:last-child) {
@apply mb-4;
margin-bottom: 1rem;
}
}
pre:last-child {
@apply mb-0;
margin-bottom: 0;
}
/* Emojis */
img.emojione {
@apply w-5 h-5 m-0;
width: 1.25rem;
height: 1.25rem;
margin: 0;
}
/* Markdown inline images (Pleroma) */
img:not(.emojione) {
@apply max-h-[500px] mx-auto rounded-sm;
max-height: 30rem;
margin: 0 auto;
border-radius: 0.125rem;
}
&.big-emoji img.emojione {
@apply inline w-9 h-9 p-1;
display: inline;
width: 2.25rem;
height: 2.25rem;
padding: 0.25rem;
}
.status-link {
@apply hover:underline text-primary-600 dark:text-primary-400 hover:text-primary-800 dark:hover:text-primary-400;
color: rgb(var(--color-primary-600));
&:hover {
color: rgb(var(--color-primary-800));
text-decoration: underline;
}
.dark {
color: rgb(var(--color-primary-400));
&:hover {
color: rgb(var(--color-primary-400));
}
}
}
span.invisible {
@@ -180,7 +245,7 @@ body.underline-links {
[data-markup],
[data-lexical-editor] {
a {
@apply underline;
text-decoration: underline;
}
}
}

View File

@@ -42,6 +42,32 @@
}
}
.-account-stats {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
align-items: center;
a, div {
display: flex;
gap: 0.25rem;
align-items: center;
@include mixins.text($weight: bold, $size: sm);
strong {
color: rgb(var(--color-primary-600));
.dark * & {
color: rgb(var(--color-primary-400));
}
}
}
a:hover {
text-decoration: underline;
}
}
.-account-card {
display: block;
flex-shrink: 0;

View File

@@ -31,20 +31,43 @@
background-color: transparent !important;
}
&__editor__placeholder {
pointer-events: none;
user-select: none;
&__editor {
position: relative;
position: absolute;
top: 0;
&__placeholder {
pointer-events: none;
user-select: none;
font-size: 1rem;
color: rgb(var(--color-gray-600));
position: absolute;
top: 0;
visibility: visible !important;
.dark &::placeholder {
font-size: 1rem;
color: rgb(var(--color-gray-600));
visibility: visible !important;
.dark &::placeholder {
color: rgb(var(--color-gray-600));
}
}
&__editable {
position: relative;
z-index: 10;
min-height: 99px;
font-size: 1rem;
&--condensed {
min-height: 39px;
}
.no-reduce-motion & {
transition: min-height 150ms var(--ease-default);
}
&:focus-visible {
outline: none;
}
}
}
@@ -53,7 +76,7 @@
padding-right: 0.75rem;
padding-bottom: 0.5rem;
padding-left: 0.75rem;
border-color: rgb(var(--color-gray-400));
border: 1px solid rgb(var(--color-gray-400));
border-radius: 0.375rem;
outline: 2px solid transparent;
@@ -114,6 +137,34 @@
align-items: center;
}
&__button {
color: rgb(var(--color-gray-600));
&:hover {
color: rgb(var(--color-gray-700));
}
.dark &:hover {
color: rgb(var(--color-gray-500));
}
&[aria-pressed='true'] {
color: rgb(var(--color-primary-500));
&:hover {
color: rgb(var(--color-primary-600));
}
.dark & {
color: rgb(var(--color-primary-500));
&:hover {
color: rgb(var(--color-primary-400));
}
}
}
}
&__actions {
display: flex;
gap: 1rem;
@@ -132,7 +183,7 @@
align-items: center;
}
&__button {
&__send-button {
user-select: none;
display: inline-flex;

View File

@@ -217,7 +217,9 @@
}
}
.lexical {
&__editor {
position: relative;
display: block;
width: 100%;

View File

@@ -1,13 +1,22 @@
.divide-x-dot > *:not(:last-child)::after {
@apply px-1;
content: '·';
padding-right: 0.25rem;
padding-left: 0.25rem;
}
.mention {
@apply text-primary-600 dark:text-primary-400 hover:underline;
color: rgb(var(--color-primary-600));
.dark {
color: rgb(var(--color-primary-400));
}
&:hover {
text-decoration: underline;
}
}
.emoji-lg img.emojione {
@apply h-9 w-9 #{!important};
width: 2.25rem !important;
height: 2.25rem !important;
}

View File

@@ -28,6 +28,7 @@ const config = defineConfig(() => ({
},
assetsInclude: ['**/*.oga'],
server: {
host: '0.0.0.0',
port: Number(process.env.PORT ?? 7312),
hmr: process.env.HMR_DISABLED === 'true' ? false : undefined,
ws: process.env.WS_DISABLED === 'true' ? false : undefined,

View File

@@ -910,6 +910,7 @@ const getFeatures = (instance: Instance) => {
v.software === MASTODON,
v.software === MITRA,
v.software === PLEROMA,
v.software === SNAC && gte(v.version, '2.78.0'),
v.software === TOKI,
]),
@@ -1398,6 +1399,7 @@ const getFeatures = (instance: Instance) => {
v.software === MITRA && gte(v.version, '3.15.0'),
v.software === NEODB,
v.software === SHARKEY,
v.software === SNAC && gte(v.version, '2.90.0'),
v.software === TAKAHE && gte(v.version, '0.8.0'),
instance.api_versions['polls.pleroma.pl-api'] >= 1,
]),