diff --git a/src/components/compose.css b/src/components/compose.css index cbf94bc0..5b3f4ecd 100644 --- a/src/components/compose.css +++ b/src/components/compose.css @@ -24,7 +24,7 @@ color: var(--text-insignificant-color); } -#compose-container textarea{ +#compose-container textarea { width: 100%; max-width: 100%; height: 3em; @@ -114,14 +114,16 @@ #compose-container .toolbar-button:has([disabled]) > * { filter: opacity(0.3); } -#compose-container .toolbar-button:not(.show-field) :is(input[type="checkbox"], select, input[type="file"]) { +#compose-container + .toolbar-button:not(.show-field) + :is(input[type='checkbox'], select, input[type='file']) { opacity: 0; position: absolute; left: 0; height: 100%; margin: 0; } -#compose-container .toolbar-button input[type="file"] { +#compose-container .toolbar-button input[type='file'] { /* Move this out of the way, to fix cursor: pointer bug */ left: -100vw !important; } @@ -201,7 +203,7 @@ #compose-container .media-preview { flex-shrink: 1; } -#compose-container .media-preview > *{ +#compose-container .media-preview > * { min-width: 80px; width: 80px !important; height: 80px; @@ -215,6 +217,22 @@ flex-grow: 1; resize: none; } +#compose-container .media-attachments .media-desc { + flex-grow: 1; +} +#compose-container .media-attachments .media-desc p { + font-size: 90%; + margin: 0; + padding: 0; + /* clamp 2 lines */ + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} +#compose-container .media-attachments .media-desc p i { + color: var(--text-insignificant-color); +} #compose-container .media-aside { display: flex; flex-direction: column; @@ -226,7 +244,10 @@ align-self: flex-start; color: var(--text-insignificant-color); } +#compose-container .media-aside .close-button:hover { + color: var(--text-color); +} #compose-container .media-aside .uploaded { color: var(--green-color); margin-bottom: 4px; -} \ No newline at end of file +} diff --git a/src/components/compose.jsx b/src/components/compose.jsx index c1ec65bf..cfd2bd43 100644 --- a/src/components/compose.jsx +++ b/src/components/compose.jsx @@ -263,26 +263,33 @@ export default ({ onClose, replyToStatus }) => { if (mediaAttachments.length > 0) { // Upload media attachments first const mediaPromises = mediaAttachments.map((attachment) => { - const params = { - file: attachment.file, - description: attachment.description || undefined, - }; - return masto.mediaAttachments.create(params).then((res) => { - // Update media attachment with ID - if (res.id) { - attachment.id = res.id; - } - return res; - }); + const { file, description, sourceDescription, id } = + attachment; + console.log('UPLOADING', attachment); + if (id) { + // If already uploaded + return attachment; + } else { + const params = { + file, + description, + }; + return masto.mediaAttachments.create(params).then((res) => { + // Update media attachment with ID + if (res.id) { + attachment.id = res.id; + } + return res; + }); + } }); const results = await Promise.allSettled(mediaPromises); // If any failed, return if ( - results.some( - (result) => - result.status === 'rejected' || !result.value.id, - ) + results.some((result) => { + return result.status === 'rejected' || !result.value?.id; + }) ) { setUIState('error'); // Alert all the reasons @@ -314,7 +321,8 @@ export default ({ onClose, replyToStatus }) => { newStatus, }); } catch (e) { - alert(e); + console.error(e); + alert(e?.reason || e); setUIState('error'); } })(); @@ -410,63 +418,25 @@ export default ({ onClose, replyToStatus }) => { {mediaAttachments.length > 0 && ( <div class="media-attachments"> {mediaAttachments.map((attachment, i) => { - const { url, type, id } = attachment; - const suffixType = type.split('/')[0]; + const { id } = attachment; return ( - <div class="media-attachment" key={i + id}> - <div class="media-preview"> - {suffixType === 'image' ? ( - <img src={url} alt="" /> - ) : suffixType === 'video' ? ( - <video src={url} playsinline muted /> - ) : suffixType === 'audio' ? ( - <audio src={url} controls /> - ) : null} - </div> - <textarea - placeholder={ - { - image: 'Image description', - video: 'Video description', - audio: 'Audio description', - }[suffixType] - } - autoCapitalize="sentences" - autoComplete="on" - autoCorrect="on" - spellCheck="true" - dir="auto" - disabled={uiState === 'loading'} - maxlength="1500" - // TODO: Un-hard-code this maxlength, ref: https://github.com/mastodon/mastodon/blob/b59fb28e90bc21d6fd1a6bafd13cfbd81ab5be54/app/models/media_attachment.rb#L39 - onInput={(e) => { - const { value } = e.target; - // Modify `description` in media attachment - setMediaAttachments((attachments) => { - const newAttachments = [...attachments]; - newAttachments[i].description = value; - return newAttachments; - }); - }} - ></textarea> - <div class="media-aside"> - <button - type="button" - class="plain close-button" - disabled={uiState === 'loading'} - onClick={() => { - setMediaAttachments((attachments) => { - return attachments.filter((_, j) => j !== i); - }); - }} - > - <Icon icon="x" /> - </button> - {!!id && ( - <Icon icon="upload" title="Uploaded" class="uploaded" /> - )} - </div> - </div> + <MediaAttachment + key={i + id} + attachment={attachment} + disabled={uiState === 'loading'} + onDescriptionChange={(value) => { + setMediaAttachments((attachments) => { + const newAttachments = [...attachments]; + newAttachments[i].description = value; + return newAttachments; + }); + }} + onRemove={() => { + setMediaAttachments((attachments) => { + return attachments.filter((_, j) => j !== i); + }); + }} + /> ); })} </div> @@ -529,3 +499,65 @@ export default ({ onClose, replyToStatus }) => { </div> ); }; + +function MediaAttachment({ + attachment, + disabled, + onDescriptionChange = () => {}, + onRemove = () => {}, +}) { + const { url, type, id } = attachment; + const suffixType = type.split('/')[0]; + return ( + <div class="media-attachment"> + <div class="media-preview"> + {suffixType === 'image' ? ( + <img src={url} alt="" /> + ) : suffixType === 'video' ? ( + <video src={url} playsinline muted /> + ) : suffixType === 'audio' ? ( + <audio src={url} controls /> + ) : null} + </div> + {!!id ? ( + <div class="media-desc"> + <span class="tag">Uploaded</span> + <p>{attachment.description || <i>No description</i>}</p> + </div> + ) : ( + <textarea + value={attachment.description || ''} + placeholder={ + { + image: 'Image description', + video: 'Video description', + audio: 'Audio description', + }[suffixType] + } + autoCapitalize="sentences" + autoComplete="on" + autoCorrect="on" + spellCheck="true" + dir="auto" + disabled={disabled} + maxlength="1500" // Not unicode-aware :( + // TODO: Un-hard-code this maxlength, ref: https://github.com/mastodon/mastodon/blob/b59fb28e90bc21d6fd1a6bafd13cfbd81ab5be54/app/models/media_attachment.rb#L39 + onInput={(e) => { + const { value } = e.target; + onDescriptionChange(value); + }} + ></textarea> + )} + <div class="media-aside"> + <button + type="button" + class="plain close-button" + disabled={disabled} + onClick={onRemove} + > + <Icon icon="x" /> + </button> + </div> + </div> + ); +} diff --git a/src/components/status.css b/src/components/status.css index 2b10cc36..c4906fbd 100644 --- a/src/components/status.css +++ b/src/components/status.css @@ -1,23 +1,28 @@ /* REBLOG + REPLY-TO */ .status-reblog { - background: linear-gradient(to bottom right, var( - --reblog-faded-color - ), transparent 160px); + background: linear-gradient( + to bottom right, + var(--reblog-faded-color), + transparent 160px + ); } .status-reply-to { - background: linear-gradient(to bottom right, var( - --reply-to-faded-color - ), transparent 160px); + background: linear-gradient( + to bottom right, + var(--reply-to-faded-color), + transparent 160px + ); } .status-reblog .status-reply-to { - background: linear-gradient(to top left, var( - --reply-to-faded-color - ), transparent 160px); + background: linear-gradient( + to top left, + var(--reply-to-faded-color), + transparent 160px + ); } .visibility-direct { - /* diagonal stripes of yellow */ - background-image: repeating-linear-gradient( + --yellow-stripes: repeating-linear-gradient( -45deg, var(--reply-to-faded-color), var(--reply-to-faded-color) 10px, @@ -25,6 +30,8 @@ transparent 10px, transparent 20px ); + /* diagonal stripes of yellow */ + background-image: var(--yellow-stripes); } /* STATUS PRE META */ @@ -51,7 +58,18 @@ align-items: flex-start; } .status.large { + --fade-in-out-bg: linear-gradient( + to bottom, + transparent, + var(--bg-color) 70px, + var(--bg-color) calc(100% - 50px), + transparent + ); padding-bottom: 8px; + background-image: var(--fade-in-out-bg); +} +.status.large.visibility-direct { + background-image: var(--fade-in-out-bg), var(--yellow-stripes); } .status-pre-meta + .status { padding-top: 8px; @@ -87,11 +105,11 @@ min-height: 50px; justify-content: space-between; } -.status > .container > .meta .arrow { +.status > .container > .meta .arrow { color: var(--reply-to-color); vertical-align: middle; } -.status > .container > .meta :is(.time, .edited) { +.status > .container > .meta :is(.time, .edited) { color: inherit; text-align: end; opacity: 0.5; @@ -100,17 +118,16 @@ margin-left: 4px; white-space: nowrap; } -.status > .container > .meta a.time:hover { +.status > .container > .meta a.time:hover { text-decoration: underline; } -.status > .container > .meta .reply-to { +.status > .container > .meta .reply-to { opacity: 0.5; font-size: smaller; } .status.large .content-container { margin-left: calc(-50px - 16px); - background-image: linear-gradient(to bottom, transparent, var(--bg-color) 10px, var(--bg-color)); padding-top: 10px; padding-bottom: 10px; } @@ -124,13 +141,13 @@ align-items: center; } .status .content-container.has-spoiler .spoiler ~ * { - filter: blur(6px) invert(.5); + filter: blur(6px) invert(0.5); pointer-events: none; - transition: filter .5s; + transition: filter 0.5s; user-select: none; } .status .content-container.has-spoiler .spoiler ~ .content ~ * { - opacity: .5; + opacity: 0.5; } .status .content-container.show-spoiler .spoiler { border-style: dotted; @@ -148,7 +165,7 @@ margin-top: 8px; } .status .content p { - margin-block: .75em; + margin-block: 0.75em; } .status .content p:first-child { margin-block-start: 0; @@ -236,7 +253,7 @@ height: 70px; border-radius: 50%; background-color: var(--bg-blur-color); - backdrop-filter: blur(6px) saturate(3) invert(.2); + backdrop-filter: blur(6px) saturate(3) invert(0.2); z-index: 1; } .status .media-video:after { @@ -249,8 +266,9 @@ width: 0; height: 0; border-style: solid; - border-width: 15px 0 15px 26.0px; - border-color: transparent transparent transparent var(--text-insignificant-color); + border-width: 15px 0 15px 26px; + border-color: transparent transparent transparent + var(--text-insignificant-color); pointer-events: none; opacity: 0.75; z-index: 2; @@ -305,7 +323,7 @@ overflow: hidden; display: -webkit-box; display: box; - -webkit-box-orient: vertical; + -webkit-box-orient: vertical; box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; @@ -318,7 +336,7 @@ overflow: hidden; display: -webkit-box; display: box; - -webkit-box-orient: vertical; + -webkit-box-orient: vertical; box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; @@ -351,9 +369,15 @@ a.card:hover { display: flex; gap: 8px; justify-content: space-between; - background-image: linear-gradient(to right, var(--link-faded-color), var(--link-faded-color) var(--percentage), transparent var(--percentage), transparent); + background-image: linear-gradient( + to right, + var(--link-faded-color), + var(--link-faded-color) var(--percentage), + transparent var(--percentage), + transparent + ); border-radius: 8px; - border: 1px solid rgba(128, 128, 128, .1); + border: 1px solid rgba(128, 128, 128, 0.1); align-items: center; } .poll-label { @@ -391,8 +415,7 @@ a.card:hover { } .status.large .extra-meta { padding-top: 0; - margin-left: calc(-50px - 16px); - background-color: var(--bg-color); + margin-left: calc(-50px - 4px); } /* ACTIONS */ @@ -405,14 +428,12 @@ a.card:hover { justify-content: space-between; } .status.large .actions { - /* margin-left: -12px; */ padding-top: 8px; padding-bottom: 16px; margin-left: calc(-50px - 16px); - background-image: linear-gradient(to bottom, var(--bg-color), var(--bg-color) calc(100% - 10px), transparent); } .status .actions > * { - opacity: .5; + opacity: 0.5; transition: opacity 0.2s ease-in-out; } .status:hover .actions > * { @@ -459,9 +480,11 @@ a.card:hover { width: 100%; font-size: 90%; border: 1px solid var(--outline-color); - background: linear-gradient(to bottom right, var( - --bg-faded-color - ), transparent 160px); + background: linear-gradient( + to bottom right, + var(--bg-faded-color), + transparent 160px + ); } /* MISC */ @@ -485,7 +508,7 @@ a.card:hover { min-height: 50dvh; } -#edit-history :is(ol, ol li){ +#edit-history :is(ol, ol li) { list-style: none; margin: 0; padding: 0;