diff --git a/src/app.css b/src/app.css index dcdab09d..d8557bd6 100644 --- a/src/app.css +++ b/src/app.css @@ -1,4 +1,5 @@ -html, body { +html, +body { margin: 0; padding: 0; background-color: var(--bg-color); @@ -23,10 +24,10 @@ a.mention span { text-decoration-line: underline; text-decoration-color: inherit; } -a.hashtag { +a.mention:has(span).hashtag { color: var(--link-light-color); } -:is(a.hashtag, a.u-url) span{ +a.mention span { color: var(--text-color); } @@ -36,7 +37,7 @@ a.hashtag { height: 100dvh; overflow: auto; overflow-x: hidden; - transition: opacity .1s ease-in-out; + transition: opacity 0.1s ease-in-out; } .deck-container[hidden] { display: block; @@ -103,7 +104,7 @@ a.hashtag { .deck h2 { font-size: 1.45em; } -.deck.padded-bottom .timeline li:last-child { +.deck.padded-bottom .timeline > li:last-child { padding-bottom: 80vh; } @@ -111,13 +112,13 @@ a.hashtag { margin: 0 auto; padding: 0; } -.timeline li { +.timeline > li { list-style: none; margin: 0; padding: 0; border-bottom: 1px solid var(--divider-color); } -.timeline.flat li { +.timeline.flat > li { border-bottom: none; } /* .timeline li.insignificant { @@ -131,23 +132,31 @@ a.hashtag { opacity: 1; } */ -.timeline.contextual li { +.timeline.contextual > li { --width: 3px; --left: 40px; --right: calc(var(--left) + var(--width)); - background-image: linear-gradient(to right, transparent, transparent var(--left), var(--comment-line-color) var(--left), var(--comment-line-color) var(--right), transparent var(--right), transparent); + background-image: linear-gradient( + to right, + transparent, + transparent var(--left), + var(--comment-line-color) var(--left), + var(--comment-line-color) var(--right), + transparent var(--right), + transparent + ); background-repeat: no-repeat; } -.timeline.contextual li:first-child { +.timeline.contextual > li:first-child { background-position: 0 16px; } -.timeline.contextual li:last-child { +.timeline.contextual > li:last-child { background-size: 100% 20px; } -.timeline.contextual li.descendant { +.timeline.contextual > li.descendant { position: relative; } -.timeline.contextual li.descendant.indirect:before { +.timeline.contextual > li.descendant.indirect:before { --radius: 10px; --diameter: calc(var(--radius) * 2); content: ''; @@ -162,7 +171,7 @@ a.hashtag { border-color: transparent transparent var(--comment-line-color) transparent; transform: rotate(45deg); } -.timeline.contextual li.descendant.indirect .status-link { +.timeline.contextual > li.descendant.indirect .status-link { position: relative; } @@ -172,7 +181,11 @@ a.hashtag { .timeline-deck.compact .status { max-height: max(25vh, 160px); overflow: hidden; - mask-image: linear-gradient(rgba(0, 0, 0, 1), rgba(0, 0, 0, 1) 80%, transparent 95%); + mask-image: linear-gradient( + rgba(0, 0, 0, 1), + rgba(0, 0, 0, 1) 80%, + transparent 95% + ); } .timeline-deck.compact .status .meta ~ * { pointer-events: none; @@ -221,7 +234,7 @@ a.hashtag { } .deck-backdrop > a { flex-grow: 1; - backdrop-filter: saturate(.75); + backdrop-filter: saturate(0.75); } @keyframes slide-in { 0% { @@ -258,7 +271,7 @@ a.hashtag { } :is(button, .button).plain.has-badge:after { - content: ""; + content: ''; display: inline-block; position: absolute; right: 10px; @@ -266,7 +279,7 @@ a.hashtag { height: 4px; border-radius: 50%; background-color: var(--link-color); - opacity: .5; + opacity: 0.5; } @keyframes fade-from-top { @@ -311,7 +324,11 @@ a.hashtag { } .box-shadow { - box-shadow: 0px 36px 89px rgb(0 0 0 / 4%), 0px 23.3333px 52.1227px rgb(0 0 0 / 3%), 0px 13.8667px 28.3481px rgb(0 0 0 / 2%), 0px 7.2px 14.4625px rgb(0 0 0 / 2%), 0px 2.93333px 7.25185px rgb(0 0 0 / 2%), 0px 0.666667px 3.50231px rgb(0 0 0 / 1%); + box-shadow: 0px 36px 89px rgb(0 0 0 / 4%), + 0px 23.3333px 52.1227px rgb(0 0 0 / 3%), + 0px 13.8667px 28.3481px rgb(0 0 0 / 2%), 0px 7.2px 14.4625px rgb(0 0 0 / 2%), + 0px 2.93333px 7.25185px rgb(0 0 0 / 2%), + 0px 0.666667px 3.50231px rgb(0 0 0 / 1%); } /* CAROUSEL */ @@ -375,11 +392,11 @@ button.carousel-button[hidden] { } .carousel-dots { border-radius: 999px; - backdrop-filter: blur(12px) invert(.25) brightness(1.5); + backdrop-filter: blur(12px) invert(0.25) brightness(1.5); } @media (prefers-color-scheme: dark) { .carousel-dots { - backdrop-filter: blur(12px) brightness(.5); + backdrop-filter: blur(12px) brightness(0.5); } } button.carousel-dot { @@ -395,7 +412,7 @@ button.carousel-dot[disabled].active { button.carousel-dot.active, button.carousel-dot[disabled].active { opacity: 1; - transform: scale(2) translateY(-.5px); + transform: scale(2) translateY(-0.5px); } @media (hover: hover) { .carousel-top-controls { @@ -432,7 +449,7 @@ button.carousel-dot[disabled].active { box-shadow: 0 0 32px var(--bg-color); z-index: 1; border: 1px solid var(--bg-color); - opacity: .75; + opacity: 0.75; } /* SHEET */ @@ -475,8 +492,59 @@ button.carousel-dot[disabled].active { align-self: center; } +/* MENU POPUP */ + +.menu-container { + position: relative; +} +.menu-container button { + color: inherit !important; +} +.menu-container button:is(:hover, :active, :focus) { + background-color: var(--button-plain-bg-hover-color); +} +.menu-container menu { + position: absolute; + right: 0; + top: 0; + transform: translateY(-100%); + opacity: 0; + pointer-events: none; + padding: 8px 0; + margin: 0; + font-size: 16px; + background-color: var(--bg-color); + width: 10em; + list-style: none; + z-index: 100; + border: 1px solid var(--outline-color); + border-radius: 8px; + transition: all 0.2s ease-in-out; +} +.menu-container menu li { + margin: 0; + padding: 0; + list-style: none; +} +.menu-container > button:is(:active, :focus) + menu, +.menu-container menu:is(:hover, :active) { + opacity: 1; + pointer-events: auto; +} +.menu-container menu button { + width: 100%; + text-align: left; + color: var(--text-color) !important; + border-radius: 0; +} +.menu-container menu button:hover { + color: var(--bg-color) !important; + background-color: var(--link-color); +} + @media (min-width: 40em) { - html, body { + html, + body { background-color: var(--bg-faded-color); } #app { @@ -498,7 +566,12 @@ button.carousel-dot[disabled].active { border-bottom: 0; background-color: var(--bg-faded-blur-color); border-bottom: 0; - mask-image: linear-gradient(rgba(0, 0, 0, 1) 50%, rgba(0, 0, 0, .7) 80%, rgba(0, 0, 0, .5) 90%, transparent); + mask-image: linear-gradient( + rgba(0, 0, 0, 1) 50%, + rgba(0, 0, 0, 0.7) 80%, + rgba(0, 0, 0, 0.5) 90%, + transparent + ); } .deck header h1 { font-size: 1.5em; @@ -517,4 +590,4 @@ button.carousel-dot[disabled].active { :is(.carousel-top-controls, .carousel-controls) { padding: 32px; } -} \ No newline at end of file +} diff --git a/src/app.jsx b/src/app.jsx index c3688cad..a8a17a04 100644 --- a/src/app.jsx +++ b/src/app.jsx @@ -266,6 +266,7 @@ export function App() { ? snapStates.showCompose.replyToStatus : null } + editStatus={snapStates.showCompose?.editStatus || null} onClose={(result) => { states.showCompose = false; if (result) { diff --git a/src/components/compose.jsx b/src/components/compose.jsx index cfd2bd43..b9e8678b 100644 --- a/src/components/compose.jsx +++ b/src/components/compose.jsx @@ -17,7 +17,7 @@ import Status from './status'; - Max character limit includes BOTH status text and Content Warning text */ -export default ({ onClose, replyToStatus }) => { +export default ({ onClose, replyToStatus, editStatus }) => { const [uiState, setUIState] = useState('default'); const accounts = store.local.getJSON('accounts'); @@ -70,6 +70,32 @@ export default ({ onClose, replyToStatus }) => { return () => clearTimeout(timer); }, []); + useEffect(() => { + console.log({ editStatus }); + if (editStatus) { + const { visibility, sensitive, mediaAttachments } = editStatus; + setUIState('loading'); + (async () => { + try { + const statusSource = await masto.statuses.fetchSource(editStatus.id); + console.log({ statusSource }); + const { text, spoilerText } = statusSource; + textareaRef.current.value = text; + textareaRef.current.dataset.source = text; + spoilerTextRef.current.value = spoilerText; + setVisibility(visibility); + setSensitive(sensitive); + setMediaAttachments(mediaAttachments); + setUIState('default'); + } catch (e) { + console.error(e); + alert(e?.reason || e); + setUIState('error'); + } + })(); + } + }, [editStatus]); + const textExpanderRef = useRef(); const textExpanderTextRef = useRef(''); useEffect(() => { @@ -168,7 +194,12 @@ export default ({ onClose, replyToStatus }) => { 'You have unsaved changes. Are you sure you want to discard this post?'; const canClose = () => { // check for status or mediaAttachments - if (textareaRef.current.value || mediaAttachments.length > 0) { + const { value, dataset } = textareaRef.current; + const containNonIDMediaAttachments = + mediaAttachments.length > 0 && + mediaAttachments.some((media) => !media.id); + + if (value !== dataset?.source || containNonIDMediaAttachments) { const yes = confirm(beforeUnloadCopy); return yes; } @@ -275,7 +306,6 @@ export default ({ onClose, replyToStatus }) => { description, }; return masto.mediaAttachments.create(params).then((res) => { - // Update media attachment with ID if (res.id) { attachment.id = res.id; } @@ -306,14 +336,22 @@ export default ({ onClose, replyToStatus }) => { const params = { status, - visibility, - sensitive, spoilerText, - inReplyToId: replyToStatus?.id || undefined, + sensitive, mediaIds: mediaAttachments.map((attachment) => attachment.id), }; + if (!editStatus) { + params.visibility = visibility; + params.inReplyToId = replyToStatus?.id || undefined; + } console.log('POST', params); - const newStatus = await masto.statuses.create(params); + + let newStatus; + if (editStatus) { + newStatus = await masto.statuses.update(editStatus.id, params); + } else { + newStatus = await masto.statuses.create(params); + } setUIState('default'); // Close @@ -348,7 +386,7 @@ export default ({ onClose, replyToStatus }) => { { const sensitive = e.target.checked; setSensitive(sensitive); @@ -374,7 +412,7 @@ export default ({ onClose, replyToStatus }) => { onChange={(e) => { setVisibility(e.target.value); }} - disabled={uiState === 'loading'} + disabled={uiState === 'loading' || !!editStatus} > Public @@ -485,7 +523,7 @@ export default ({ onClose, replyToStatus }) => { - {uiState === 'loading' && }{' '} + {uiState === 'loading' && }{' '} {}, onRemove = () => {}, }) { - const { url, type, id } = attachment; + const { url, type, id, description } = attachment; const suffixType = type.split('/')[0]; return ( @@ -522,11 +560,11 @@ function MediaAttachment({ {!!id ? ( Uploaded - {attachment.description || No description} + {description || No description} ) : ( { diff --git a/src/components/status.css b/src/components/status.css index 6f1934e0..f4855ca6 100644 --- a/src/components/status.css +++ b/src/components/status.css @@ -433,13 +433,7 @@ a.card:hover { padding-top: 8px; padding-bottom: 16px; margin-left: calc(-50px - 16px); -} -.status .actions > * { - opacity: 0.5; - transition: opacity 0.2s ease-in-out; -} -.status:hover .actions > * { - opacity: 1; + color: var(--text-insignificant-color); } .status .actions > button { min-height: 40px; @@ -447,7 +441,7 @@ a.card:hover { padding: 0 8px; } .status .actions > button.plain { - color: var(--text-insignificant-color); + color: inherit; } .status .actions > button.plain:hover { color: var(--link-color); diff --git a/src/components/status.jsx b/src/components/status.jsx index 07984ee4..16a738c7 100644 --- a/src/components/status.jsx +++ b/src/components/status.jsx @@ -2,7 +2,7 @@ import './status.css'; import { getBlurHashAverageColor } from 'fast-blurhash'; import mem from 'mem'; -import { useEffect, useRef, useState } from 'preact/hooks'; +import { useEffect, useMemo, useRef, useState } from 'preact/hooks'; import { InView } from 'react-intersection-observer'; import { useSnapshot } from 'valtio'; @@ -12,6 +12,7 @@ import NameText from '../components/name-text'; import enhanceContent from '../utils/enhance-content'; import shortenNumber from '../utils/shorten-number'; import states from '../utils/states'; +import store from '../utils/store'; import visibilityIconsMap from '../utils/visibility-icons-map'; import Avatar from './avatar'; @@ -81,7 +82,8 @@ function Media({ media, showOriginal, onClick }) { height={height} style={ !showOriginal && { - backgroundColor: `rgb(${rgbAverageColor.join(',')})`, + backgroundColor: + rgbAverageColor && `rgb(${rgbAverageColor.join(',')})`, backgroundPosition: focalBackgroundPosition || 'center', } } @@ -95,7 +97,8 @@ function Media({ media, showOriginal, onClick }) { { if (isGIF) { @@ -527,6 +530,11 @@ function Status({ const createdAtDate = new Date(createdAt); const editedAtDate = new Date(editedAt); + const isSelf = useMemo(() => { + const currentAccount = store.session.get('currentAccount'); + return currentAccount && currentAccount === accountId; + }, [accountId]); + let inReplyToAccountRef = mentions?.find( (mention) => mention.id === inReplyToAccountId, ); @@ -933,6 +941,32 @@ function Status({ alt={bookmarked ? 'Bookmarked' : 'Bookmark'} /> + {isSelf && ( + + + + + + {isSelf && ( + + { + e.preventDefault(); + e.stopPropagation(); + states.showCompose = { + editStatus: status, + }; + }} + > + Edit… + + + )} + + + )} > )} @@ -961,7 +995,9 @@ function Status({ * { :is(button, .button).plain2 { background-color: transparent; color: var(--link-color); - backdrop-filter: blur(12px) invert(.25) brightness(1.5); + backdrop-filter: blur(12px) invert(0.25) brightness(1.5); } :is(button, .button).light { background-color: var(--bg-faded-color); @@ -142,17 +155,24 @@ button > * { padding: 12px; } -input[type="text"], textarea, select { +input[type='text'], +textarea, +select { color: var(--text-color); background-color: var(--bg-color); border: 2px solid var(--divider-color); padding: 8px; border-radius: 4px; } -input[type="text"]:focus, textarea:focus, select:focus { +input[type='text']:focus, +textarea:focus, +select:focus { border-color: var(--outline-color); } -input[type="text"].large, textarea.large, select.large, button.large { +input[type='text'].large, +textarea.large, +select.large, +button.large { font-size: 125%; padding: 12px; } @@ -168,15 +188,17 @@ select.plain { } @media (prefers-color-scheme: dark) { - img, video { + img, + video { filter: brightness(0.7); transition: filter 0.3s ease-out; } - img:hover, video:hover { + img:hover, + video:hover { filter: brightness(1); } :is(button, .button).plain2 { - backdrop-filter: blur(12px) brightness(.5); + backdrop-filter: blur(12px) brightness(0.5); } } @@ -197,4 +219,4 @@ select.plain { opacity: 1; transform: translateY(0); } -} \ No newline at end of file +}
{attachment.description || No description}
{description || No description}