diff --git a/src/app.css b/src/app.css
index afd4d5c9..9f92d714 100644
--- a/src/app.css
+++ b/src/app.css
@@ -1423,6 +1423,10 @@ body:has(.media-modal-container + .status-deck) .media-post-link {
display: inline-block;
margin: 4px;
align-self: center;
+
+ &.clickable {
+ cursor: pointer;
+ }
}
.tag .icon {
vertical-align: middle;
diff --git a/src/components/media-alt-modal.jsx b/src/components/media-alt-modal.jsx
new file mode 100644
index 00000000..95745764
--- /dev/null
+++ b/src/components/media-alt-modal.jsx
@@ -0,0 +1,53 @@
+import { Menu, MenuItem } from '@szhsin/react-menu';
+import { useState } from 'preact/hooks';
+
+import Icon from './icon';
+import TranslationBlock from './translation-block';
+
+export default function MediaAltModal({ alt, onClose }) {
+ const [forceTranslate, setForceTranslate] = useState(false);
+ return (
+
+ {!!onClose && (
+
+ )}
+
+
+
+ {alt}
+
+ {forceTranslate && (
+
+ )}
+
+
+ );
+}
diff --git a/src/components/media-modal.jsx b/src/components/media-modal.jsx
index 4290035e..7ec4769c 100644
--- a/src/components/media-modal.jsx
+++ b/src/components/media-modal.jsx
@@ -1,4 +1,4 @@
-import { Menu, MenuItem } from '@szhsin/react-menu';
+import { Menu } from '@szhsin/react-menu';
import { getBlurHashAverageColor } from 'fast-blurhash';
import { useEffect, useLayoutEffect, useRef, useState } from 'preact/hooks';
import { useHotkeys } from 'react-hotkeys-hook';
@@ -6,9 +6,9 @@ import { useHotkeys } from 'react-hotkeys-hook';
import Icon from './icon';
import Link from './link';
import Media from './media';
+import MediaAltModal from './media-alt-modal';
import MenuLink from './menu-link';
import Modal from './modal';
-import TranslationBlock from './translation-block';
function MediaModal({
mediaAttachments,
@@ -288,52 +288,4 @@ function MediaModal({
);
}
-function MediaAltModal({ alt, onClose }) {
- const [forceTranslate, setForceTranslate] = useState(false);
- return (
-
- {!!onClose && (
-
- )}
-
-
-
- {alt}
-
- {forceTranslate && (
-
- )}
-
-
- );
-}
-
export default MediaModal;
diff --git a/src/components/media.jsx b/src/components/media.jsx
index c661757b..bab23396 100644
--- a/src/components/media.jsx
+++ b/src/components/media.jsx
@@ -9,6 +9,8 @@ import {
} from 'preact/hooks';
import QuickPinchZoom, { make3dTransformValue } from 'react-quick-pinch-zoom';
+import states from '../utils/states';
+
import Icon from './icon';
import Link from './link';
import { formatDuration } from './status';
@@ -25,6 +27,27 @@ video = Video clip
audio = Audio track
*/
+const dataAltLabel = 'ALT';
+const AltBadge = (props) => {
+ const { alt, ...rest } = props;
+ if (!alt || !alt.trim()) return null;
+ return (
+
+ );
+};
+
function Media({ media, to, showOriginal, autoAnimate, onClick = () => {} }) {
const {
blurhash,
@@ -157,6 +180,7 @@ function Media({ media, to, showOriginal, autoAnimate, onClick = () => {} }) {
class={`media media-image`}
onClick={onClick}
data-orientation={orientation}
+ data-has-alt={!!description || undefined}
style={
showOriginal
? {
@@ -193,36 +217,39 @@ function Media({ media, to, showOriginal, autoAnimate, onClick = () => {} }) {
/>
) : (
- {
- e.target.closest('.media-image').style.backgroundImage = '';
- e.target.dataset.loaded = true;
- }}
- onError={(e) => {
- const { src } = e.target;
- if (src === mediaURL) {
- e.target.src = remoteMediaURL;
- }
- }}
- />
+ <>
+ {
+ e.target.closest('.media-image').style.backgroundImage = '';
+ e.target.dataset.loaded = true;
+ }}
+ onError={(e) => {
+ const { src } = e.target;
+ if (src === mediaURL) {
+ e.target.src = remoteMediaURL;
+ }
+ }}
+ />
+
+ >
)}
);
@@ -264,6 +291,7 @@ function Media({ media, to, showOriginal, autoAnimate, onClick = () => {} }) {
data-orientation={orientation}
data-formatted-duration={formattedDuration}
data-label={isGIF && !showOriginal && !autoGIFAnimate ? 'GIF' : ''}
+ data-has-alt={!!description || undefined}
// style={{
// backgroundColor:
// rgbAverageColor && `rgb(${rgbAverageColor.join(',')})`,
@@ -339,11 +367,14 @@ function Media({ media, to, showOriginal, autoAnimate, onClick = () => {} }) {
>
)}
+ {!showOriginal && !showInlineDesc && }
{showInlineDesc && (
{
- location.hash = to;
+ onClick={(e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ states.showMediaAlt = description;
}}
>
{description}
@@ -357,6 +388,7 @@ function Media({ media, to, showOriginal, autoAnimate, onClick = () => {} }) {
@@ -373,9 +405,12 @@ function Media({ media, to, showOriginal, autoAnimate, onClick = () => {} }) {
/>
) : null}
{!showOriginal && (
-
-
-
+ <>
+
+
+
+
+ >
)}
);
diff --git a/src/components/modals.jsx b/src/components/modals.jsx
index bab9952c..70b56eb9 100644
--- a/src/components/modals.jsx
+++ b/src/components/modals.jsx
@@ -11,6 +11,7 @@ import AccountSheet from './account-sheet';
import Compose from './compose';
import Drafts from './drafts';
import GenericAccounts from './generic-accounts';
+import MediaAltModal from './media-alt-modal';
import MediaModal from './media-modal';
import Modal from './modal';
import ShortcutsSettings from './shortcuts-settings';
@@ -178,6 +179,23 @@ export default function Modals() {
/>
)}
+ {!!snapStates.showMediaAlt && (
+ {
+ if (e.target === e.currentTarget) {
+ states.showMediaAlt = false;
+ }
+ }}
+ >
+ {
+ states.showMediaAlt = false;
+ }}
+ />
+
+ )}
>
);
}
diff --git a/src/components/status.css b/src/components/status.css
index 911d9b0c..aa00d5fd 100644
--- a/src/components/status.css
+++ b/src/components/status.css
@@ -723,6 +723,7 @@
-webkit-line-clamp: 2;
line-clamp: 2;
line-height: 1.2;
+ cursor: pointer;
}
}
@@ -833,7 +834,7 @@
.status .media:is(:hover, :focus) {
border-color: var(--outline-hover-color);
}
-.status .media:active {
+.status .media:active:not(:has(button:active)) {
filter: brightness(0.8);
transform: scale(0.99);
}
@@ -845,6 +846,51 @@
}
.status .media {
cursor: pointer;
+
+ &[data-has-alt] {
+ position: relative;
+
+ .tag {
+ position: absolute;
+ bottom: 8px;
+ left: 8px;
+ font-size: 12px;
+ font-weight: bold;
+ border: var(--hairline-width) solid var(--media-outline-color);
+ mix-blend-mode: luminosity;
+
+ &:before {
+ content: '';
+ position: absolute;
+ inset: -12px;
+ }
+
+ &:is(:hover, :focus):not(:active) {
+ transition: transform 0.15s ease-out;
+ transform: scale(1.15);
+ }
+ }
+
+ &:before {
+ font-size: 12px;
+ font-weight: bold;
+ pointer-events: none;
+ content: attr(data-alt-label);
+ position: absolute;
+ bottom: 8px;
+ left: 8px;
+ color: var(--media-fg-color);
+ background-color: var(--media-bg-color);
+ border: var(--hairline-width) solid var(--media-outline-color);
+ border-radius: 4px;
+ padding: 0 4px;
+ transition: opacity 0.2s ease-in-out;
+ }
+
+ &:hover:before {
+ opacity: 0.2;
+ }
+ }
}
.status .media img:is(:hover, :focus),
a:focus-visible .status .media img {
@@ -874,9 +920,9 @@ body:has(#modal-container .carousel) .status .media img:hover {
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
- color: var(--video-fg-color);
- background-color: var(--video-bg-color);
- box-shadow: inset 0 0 0 2px var(--video-outline-color);
+ color: var(--media-fg-color);
+ background-color: var(--media-bg-color);
+ box-shadow: inset 0 0 0 2px var(--media-outline-color);
display: flex;
place-content: center;
place-items: center;
@@ -893,9 +939,9 @@ body:has(#modal-container .carousel) .status .media img:hover {
position: absolute;
bottom: 8px;
right: 8px;
- color: var(--video-fg-color);
- background-color: var(--video-bg-color);
- border: var(--hairline-width) solid var(--video-outline-color);
+ color: var(--media-fg-color);
+ background-color: var(--media-bg-color);
+ border: var(--hairline-width) solid var(--media-outline-color);
border-radius: 4px;
padding: 0 4px;
}
@@ -910,9 +956,9 @@ body:has(#modal-container .carousel) .status .media img:hover {
position: absolute;
bottom: 8px;
right: 8px;
- color: var(--bg-faded-color);
- background-color: var(--text-insignificant-color);
- backdrop-filter: blur(6px) saturate(3) invert(0.2);
+ color: var(--media-fg-color);
+ background-color: var(--media-bg-color);
+ border: var(--hairline-width) solid var(--media-outline-color);
border-radius: 4px;
padding: 0 4px;
}
diff --git a/src/index.css b/src/index.css
index 4ff57833..dd6d8767 100644
--- a/src/index.css
+++ b/src/index.css
@@ -64,9 +64,9 @@
--close-button-hover-color: rgba(0, 0, 0, 1);
/* Video colors won't change based on color scheme */
- --video-fg-color: #f0f2f5;
- --video-bg-color: #242526;
- --video-outline-color: color-mix(in lch, var(--video-fg-color), transparent);
+ --media-fg-color: #f0f2f5;
+ --media-bg-color: #242526;
+ --media-outline-color: color-mix(in lch, var(--media-fg-color), transparent);
--timing-function: cubic-bezier(0.3, 0.5, 0, 1);
}
diff --git a/src/utils/states.js b/src/utils/states.js
index 55a6e618..042b3d5f 100644
--- a/src/utils/states.js
+++ b/src/utils/states.js
@@ -40,6 +40,7 @@ const states = proxy({
showShortcutsSettings: false,
showKeyboardShortcutsHelp: false,
showGenericAccounts: false,
+ showMediaAlt: false,
// Shortcuts
shortcuts: store.account.get('shortcuts') ?? [],
// Settings
@@ -141,6 +142,7 @@ export function hideAllModals() {
states.showShortcutsSettings = false;
states.showKeyboardShortcutsHelp = false;
states.showGenericAccounts = false;
+ states.showMediaAlt = false;
}
export function statusKey(id, instance) {