New feature: Edit status!

Get's a bit hacky now
This commit is contained in:
Lim Chee Aun 2022-12-12 21:54:31 +08:00
parent 111bc27f96
commit 5353a4535a
7 changed files with 232 additions and 67 deletions

View file

@ -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;

View file

@ -266,6 +266,7 @@ export function App() {
? snapStates.showCompose.replyToStatus
: null
}
editStatus={snapStates.showCompose?.editStatus || null}
onClose={(result) => {
states.showCompose = false;
if (result) {

View file

@ -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 }) => {
<input
name="sensitive"
type="checkbox"
disabled={uiState === 'loading'}
disabled={uiState === 'loading' || !!editStatus}
onChange={(e) => {
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}
>
<option value="public">
Public <Icon icon="earth" />
@ -485,7 +523,7 @@ export default ({ onClose, replyToStatus }) => {
</label>
</div>
<div>
{uiState === 'loading' && <Loader />}{' '}
{uiState === 'loading' && <Loader abrupt />}{' '}
<button
type="submit"
class="large"
@ -506,7 +544,7 @@ function MediaAttachment({
onDescriptionChange = () => {},
onRemove = () => {},
}) {
const { url, type, id } = attachment;
const { url, type, id, description } = attachment;
const suffixType = type.split('/')[0];
return (
<div class="media-attachment">
@ -522,11 +560,11 @@ function MediaAttachment({
{!!id ? (
<div class="media-desc">
<span class="tag">Uploaded</span>
<p>{attachment.description || <i>No description</i>}</p>
<p title={description}>{description || <i>No description</i>}</p>
</div>
) : (
<textarea
value={attachment.description || ''}
value={description || ''}
placeholder={
{
image: 'Image description',

View file

@ -34,6 +34,7 @@ const ICONS = {
attachment: 'mingcute:attachment-line',
upload: 'mingcute:upload-3-line',
gear: 'mingcute:settings-3-line',
more: 'mingcute:more-1-line',
};
export default ({ icon, size = 'm', alt, title, class: className = '' }) => {

View file

@ -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);

View file

@ -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 }) {
<div
class={`media media-${isGIF ? 'gif' : 'video'}`}
style={{
backgroundColor: `rgb(${rgbAverageColor.join(',')})`,
backgroundColor:
rgbAverageColor && `rgb(${rgbAverageColor.join(',')})`,
}}
onClick={(e) => {
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'}
/>
</button>
{isSelf && (
<span class="menu-container">
<button type="button" title="More" class="plain more-button">
<Icon icon="more" size="l" alt="More" />
</button>
<menu>
{isSelf && (
<li>
<button
type="button"
class="plain"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
states.showCompose = {
editStatus: status,
};
}}
>
Edit&hellip;
</button>
</li>
)}
</menu>
</span>
)}
</div>
</>
)}
@ -961,7 +995,9 @@ function Status({
<InView
class="carousel-item"
style={{
backgroundColor: `rgba(${rgbAverageColor.join(',')}, .5)`,
backgroundColor:
rgbAverageColor &&
`rgba(${rgbAverageColor.join(',')}, .5)`,
}}
tabindex="0"
key={media.id}

View file

@ -23,8 +23,8 @@
--reply-to-color: var(--orange-color);
--favourite-color: var(--red-color);
--reply-to-faded-color: #ffa6001a;
--outline-color: rgba(128, 128, 128, .2);
--outline-hover-color: rgba(128, 128, 128, .7);
--outline-color: rgba(128, 128, 128, 0.2);
--outline-hover-color: rgba(128, 128, 128, 0.7);
--divider-color: rgba(0, 0, 0, 0.1);
--backdrop-color: rgba(255, 255, 255, 0.5);
--img-bg-color: rgba(128, 128, 128, 0.2);
@ -43,6 +43,8 @@
--bg-faded-blur-color: #18191a99;
--text-color: #f0f2f5;
--text-insignificant-color: #f0f2f599;
--link-light-color: #6494ed99;
--link-faded-color: #6494ed44;
--link-bg-hover-color: #34353799;
--divider-color: rgba(255, 255, 255, 0.1);
--bg-blur-color: #24252699;
@ -52,12 +54,15 @@
}
}
*, *::before, *::after {
*,
*::before,
*::after {
box-sizing: border-box;
}
body {
font-family: system-ui, -apple-system, BlinkMacSystemFont, '.SFNSText-Regular', sans-serif;
font-family: system-ui, -apple-system, BlinkMacSystemFont, '.SFNSText-Regular',
sans-serif;
font-size: 16px;
word-wrap: break-word;
overflow-wrap: break-word;
@ -83,19 +88,27 @@ hr {
border: 0;
padding: 0;
margin: 16px 0;
background-image: linear-gradient(to right, transparent, var(--divider-color), transparent);
background-image: linear-gradient(
to right,
transparent,
var(--divider-color),
transparent
);
}
button, input, select, textarea {
button,
input,
select,
textarea {
font-family: inherit;
font-size: inherit;
line-height: inherit;
max-width: 100%;
}
button, .button {
button,
.button {
display: inline-block;
margin: 2px;
padding: 8px 12px;
border-radius: 2.5em;
border: 0;
@ -128,7 +141,7 @@ button > * {
: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);
}
}