mirror of
https://github.com/cheeaun/phanpy.git
synced 2025-02-02 14:16:39 +01:00
Replace (most) alert/confirms with alternative UI
Everything might break lol
This commit is contained in:
parent
10fa537a56
commit
ff41cd3563
11 changed files with 423 additions and 124 deletions
75
src/app.css
75
src/app.css
|
@ -1401,7 +1401,7 @@ body > .szh-menu-container {
|
||||||
animation: appear-smooth 0.15s ease-in-out;
|
animation: appear-smooth 0.15s ease-in-out;
|
||||||
width: 16em;
|
width: 16em;
|
||||||
max-width: 90vw;
|
max-width: 90vw;
|
||||||
overflow: hidden;
|
/* overflow: hidden; */
|
||||||
}
|
}
|
||||||
.szh-menu[aria-label='Submenu'] {
|
.szh-menu[aria-label='Submenu'] {
|
||||||
background-color: var(--bg-blur-color);
|
background-color: var(--bg-blur-color);
|
||||||
|
@ -1418,6 +1418,7 @@ body > .szh-menu-container {
|
||||||
text-shadow: 0 1px 0 var(--bg-color);
|
text-shadow: 0 1px 0 var(--bg-color);
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
/* border-bottom: 1px solid var(--outline-color); */
|
/* border-bottom: 1px solid var(--outline-color); */
|
||||||
|
border-radius: 8px 8px 0 0;
|
||||||
}
|
}
|
||||||
.szh-menu__header.plain {
|
.szh-menu__header.plain {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
@ -1426,6 +1427,28 @@ body > .szh-menu-container {
|
||||||
.szh-menu__header * {
|
.szh-menu__header * {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
.szh-menu.menu-emphasized {
|
||||||
|
border-color: var(--outline-hover-color);
|
||||||
|
box-shadow: 0 3px 16px -3px var(--drop-shadow-color),
|
||||||
|
0 3px 32px var(--drop-shadow-color), 0 3px 48px var(--drop-shadow-color);
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
animation-duration: 0.3s;
|
||||||
|
animation-timing-function: ease-in-out;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
.szh-menu .footer {
|
||||||
|
margin: 8px 0 -8px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
color: var(--text-insignificant-color);
|
||||||
|
font-size: 90%;
|
||||||
|
background-color: var(--bg-faded-color);
|
||||||
|
text-shadow: 0 1px 0 var(--bg-color);
|
||||||
|
line-height: 1.2;
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 0 0 8px 8px;
|
||||||
|
}
|
||||||
.szh-menu .szh-menu__item {
|
.szh-menu .szh-menu__item {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
@ -1498,21 +1521,26 @@ body > .szh-menu-container {
|
||||||
font-size: inherit;
|
font-size: inherit;
|
||||||
}
|
}
|
||||||
.szh-menu .menu-horizontal {
|
.szh-menu .menu-horizontal {
|
||||||
display: flex;
|
display: grid;
|
||||||
|
/* two columns only */
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
}
|
}
|
||||||
.szh-menu .menu-horizontal .szh-menu__item {
|
.szh-menu .menu-horizontal > .szh-menu__item:not(:only-child):first-child,
|
||||||
flex: 1;
|
.szh-menu .menu-horizontal > *:not(:only-child):first-child .szh-menu__item {
|
||||||
}
|
|
||||||
.szh-menu .menu-horizontal .szh-menu__item:not(:only-child):first-child {
|
|
||||||
padding-right: 4px !important;
|
padding-right: 4px !important;
|
||||||
}
|
}
|
||||||
.szh-menu
|
.szh-menu
|
||||||
.menu-horizontal
|
.menu-horizontal
|
||||||
.szh-menu__item:not(:only-child):not(:first-child):not(:last-child) {
|
> .szh-menu__item:not(:only-child):not(:first-child):not(:last-child),
|
||||||
|
.szh-menu
|
||||||
|
.menu-horizontal
|
||||||
|
> *:not(:only-child):not(:first-child):not(:last-child)
|
||||||
|
.szh-menu__item {
|
||||||
padding-left: 8px !important;
|
padding-left: 8px !important;
|
||||||
padding-right: 4px !important;
|
padding-right: 4px !important;
|
||||||
}
|
}
|
||||||
.szh-menu .menu-horizontal .szh-menu__item:not(:only-child):last-child {
|
.szh-menu .menu-horizontal > .szh-menu__item:not(:only-child):last-child,
|
||||||
|
.szh-menu .menu-horizontal > *:not(:only-child):last-child .szh-menu__item {
|
||||||
padding-left: 8px !important;
|
padding-left: 8px !important;
|
||||||
}
|
}
|
||||||
.szh-menu .szh-menu__item .menu-shortcut {
|
.szh-menu .szh-menu__item .menu-shortcut {
|
||||||
|
@ -1533,6 +1561,19 @@ body > .szh-menu-container {
|
||||||
color: var(--red-color);
|
color: var(--red-color);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
.szh-menu
|
||||||
|
.szh-menu__item:not(.szh-menu__item--disabled):not(
|
||||||
|
.szh-menu__item--hover
|
||||||
|
).danger {
|
||||||
|
color: var(--red-color);
|
||||||
|
}
|
||||||
|
.szh-menu
|
||||||
|
.szh-menu__item:not(.szh-menu__item--disabled):not(
|
||||||
|
.szh-menu__item--hover
|
||||||
|
).danger
|
||||||
|
.icon {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.szh-menu .menu-wrap {
|
.szh-menu .menu-wrap {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -1658,6 +1699,24 @@ meter.donut[hidden] {
|
||||||
margin-bottom: env(safe-area-inset-bottom);
|
margin-bottom: env(safe-area-inset-bottom);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* TOAST - ALERT */
|
||||||
|
|
||||||
|
:root .toastify.alert {
|
||||||
|
z-index: 1001;
|
||||||
|
box-shadow: 0 8px 32px var(--text-insignificant-color);
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
color: var(--text-color);
|
||||||
|
cursor: pointer;
|
||||||
|
pointer-events: auto;
|
||||||
|
padding: 16px 32px;
|
||||||
|
font-size: max(calc(16px * 1.1), var(--text-size));
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.25;
|
||||||
|
}
|
||||||
|
:root .toastify.alert:is(:hover, :active) {
|
||||||
|
background-color: var(--bg-faded-color);
|
||||||
|
}
|
||||||
|
|
||||||
/* AVATARS STACK */
|
/* AVATARS STACK */
|
||||||
|
|
||||||
.avatars-stack {
|
.avatars-stack {
|
||||||
|
|
|
@ -21,6 +21,7 @@ import Icon from './icon';
|
||||||
import Link from './link';
|
import Link from './link';
|
||||||
import ListAddEdit from './list-add-edit';
|
import ListAddEdit from './list-add-edit';
|
||||||
import Loader from './loader';
|
import Loader from './loader';
|
||||||
|
import MenuConfirm from './menu-confirm';
|
||||||
import Modal from './modal';
|
import Modal from './modal';
|
||||||
import TranslationBlock from './translation-block';
|
import TranslationBlock from './translation-block';
|
||||||
|
|
||||||
|
@ -734,11 +735,20 @@ function RelatedActions({ info, instance, authenticated }) {
|
||||||
</div>
|
</div>
|
||||||
</SubMenu>
|
</SubMenu>
|
||||||
)}
|
)}
|
||||||
<MenuItem
|
<MenuConfirm
|
||||||
|
subMenu
|
||||||
|
confirm={!blocking}
|
||||||
|
confirmLabel={
|
||||||
|
<>
|
||||||
|
<Icon icon="block" />
|
||||||
|
<span>Block @{username}?</span>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
menuItemClassName="danger"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!blocking && !confirm(`Block @${username}?`)) {
|
// if (!blocking && !confirm(`Block @${username}?`)) {
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
setRelationshipUIState('loading');
|
setRelationshipUIState('loading');
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
|
@ -784,7 +794,7 @@ function RelatedActions({ info, instance, authenticated }) {
|
||||||
<span>Block @{username}…</span>
|
<span>Block @{username}…</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</MenuItem>
|
</MenuConfirm>
|
||||||
{/* <MenuItem>
|
{/* <MenuItem>
|
||||||
<Icon icon="flag" />
|
<Icon icon="flag" />
|
||||||
<span>Report @{username}…</span>
|
<span>Report @{username}…</span>
|
||||||
|
@ -796,10 +806,17 @@ function RelatedActions({ info, instance, authenticated }) {
|
||||||
<Loader abrupt />
|
<Loader abrupt />
|
||||||
)}
|
)}
|
||||||
{!!relationship && (
|
{!!relationship && (
|
||||||
<button
|
<MenuConfirm
|
||||||
type="button"
|
confirm={following || requested}
|
||||||
class={`${following || requested ? 'light swap' : ''}`}
|
confirmLabel={
|
||||||
data-swap-state={following || requested ? 'danger' : ''}
|
<span>
|
||||||
|
{requested
|
||||||
|
? 'Withdraw follow request?'
|
||||||
|
: `Unfollow @${info.acct || info.username}?`}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
menuItemClassName="danger"
|
||||||
|
align="end"
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setRelationshipUIState('loading');
|
setRelationshipUIState('loading');
|
||||||
|
@ -808,18 +825,17 @@ function RelatedActions({ info, instance, authenticated }) {
|
||||||
let newRelationship;
|
let newRelationship;
|
||||||
|
|
||||||
if (following || requested) {
|
if (following || requested) {
|
||||||
const yes = confirm(
|
// const yes = confirm(
|
||||||
requested
|
// requested
|
||||||
? 'Withdraw follow request?'
|
// ? 'Withdraw follow request?'
|
||||||
: `Unfollow @${info.acct || info.username}?`,
|
// : `Unfollow @${info.acct || info.username}?`,
|
||||||
);
|
// );
|
||||||
|
|
||||||
if (yes) {
|
// if (yes) {
|
||||||
newRelationship =
|
newRelationship = await currentMasto.v1.accounts.unfollow(
|
||||||
await currentMasto.v1.accounts.unfollow(
|
accountID.current,
|
||||||
accountID.current,
|
);
|
||||||
);
|
// }
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
newRelationship = await currentMasto.v1.accounts.follow(
|
newRelationship = await currentMasto.v1.accounts.follow(
|
||||||
accountID.current,
|
accountID.current,
|
||||||
|
@ -835,24 +851,31 @@ function RelatedActions({ info, instance, authenticated }) {
|
||||||
})();
|
})();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{following ? (
|
<button
|
||||||
<>
|
type="button"
|
||||||
<span>Following</span>
|
class={`${following || requested ? 'light swap' : ''}`}
|
||||||
<span>Unfollow…</span>
|
data-swap-state={following || requested ? 'danger' : ''}
|
||||||
</>
|
disabled={loading}
|
||||||
) : requested ? (
|
>
|
||||||
<>
|
{following ? (
|
||||||
<span>Requested</span>
|
<>
|
||||||
<span>Withdraw…</span>
|
<span>Following</span>
|
||||||
</>
|
<span>Unfollow…</span>
|
||||||
) : locked ? (
|
</>
|
||||||
<>
|
) : requested ? (
|
||||||
<Icon icon="lock" /> <span>Follow</span>
|
<>
|
||||||
</>
|
<span>Requested</span>
|
||||||
) : (
|
<span>Withdraw…</span>
|
||||||
'Follow'
|
</>
|
||||||
)}
|
) : locked ? (
|
||||||
</button>
|
<>
|
||||||
|
<Icon icon="lock" /> <span>Follow</span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
'Follow'
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</MenuConfirm>
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { getCurrentAccountNS } from '../utils/store-utils';
|
||||||
|
|
||||||
import Icon from './icon';
|
import Icon from './icon';
|
||||||
import Loader from './loader';
|
import Loader from './loader';
|
||||||
|
import MenuConfirm from './menu-confirm';
|
||||||
|
|
||||||
function Drafts({ onClose }) {
|
function Drafts({ onClose }) {
|
||||||
const { masto } = api();
|
const { masto } = api();
|
||||||
|
@ -89,26 +90,33 @@ function Drafts({ onClose }) {
|
||||||
{niceDateTime(updatedAtDate)}
|
{niceDateTime(updatedAtDate)}
|
||||||
</time>
|
</time>
|
||||||
</b>
|
</b>
|
||||||
<button
|
<MenuConfirm
|
||||||
type="button"
|
confirmLabel={<span>Delete this draft?</span>}
|
||||||
class="small light"
|
menuItemClassName="danger"
|
||||||
|
align="end"
|
||||||
disabled={uiState === 'loading'}
|
disabled={uiState === 'loading'}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
const yes = confirm('Delete this draft?');
|
// const yes = confirm('Delete this draft?');
|
||||||
if (yes) {
|
// if (yes) {
|
||||||
await db.drafts.del(key);
|
await db.drafts.del(key);
|
||||||
reload();
|
reload();
|
||||||
}
|
// }
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
alert('Error deleting draft! Please try again.');
|
alert('Error deleting draft! Please try again.');
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Delete…
|
<button
|
||||||
</button>
|
type="button"
|
||||||
|
class="small light"
|
||||||
|
disabled={uiState === 'loading'}
|
||||||
|
>
|
||||||
|
Delete…
|
||||||
|
</button>
|
||||||
|
</MenuConfirm>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
@ -145,15 +153,16 @@ function Drafts({ onClose }) {
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
<p>
|
{drafts.length > 1 && (
|
||||||
<button
|
<p>
|
||||||
type="button"
|
<MenuConfirm
|
||||||
class="light danger"
|
confirmLabel={<span>Delete all drafts?</span>}
|
||||||
disabled={uiState === 'loading'}
|
menuItemClassName="danger"
|
||||||
onClick={() => {
|
disabled={uiState === 'loading'}
|
||||||
(async () => {
|
onClick={() => {
|
||||||
const yes = confirm('Delete all drafts?');
|
(async () => {
|
||||||
if (yes) {
|
// const yes = confirm('Delete all drafts?');
|
||||||
|
// if (yes) {
|
||||||
setUIState('loading');
|
setUIState('loading');
|
||||||
try {
|
try {
|
||||||
await db.drafts.delMany(
|
await db.drafts.delMany(
|
||||||
|
@ -166,13 +175,20 @@ function Drafts({ onClose }) {
|
||||||
alert('Error deleting drafts! Please try again.');
|
alert('Error deleting drafts! Please try again.');
|
||||||
setUIState('error');
|
setUIState('error');
|
||||||
}
|
}
|
||||||
}
|
// }
|
||||||
})();
|
})();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Delete all drafts…
|
<button
|
||||||
</button>
|
type="button"
|
||||||
</p>
|
class="light danger"
|
||||||
|
disabled={uiState === 'loading'}
|
||||||
|
>
|
||||||
|
Delete all…
|
||||||
|
</button>
|
||||||
|
</MenuConfirm>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<p>No drafts found.</p>
|
<p>No drafts found.</p>
|
||||||
|
|
|
@ -87,6 +87,7 @@ const ICONS = {
|
||||||
layout4: () => import('@iconify-icons/mingcute/layout-4-line'),
|
layout4: () => import('@iconify-icons/mingcute/layout-4-line'),
|
||||||
layout5: () => import('@iconify-icons/mingcute/layout-5-line'),
|
layout5: () => import('@iconify-icons/mingcute/layout-5-line'),
|
||||||
announce: () => import('@iconify-icons/mingcute/announcement-line'),
|
announce: () => import('@iconify-icons/mingcute/announcement-line'),
|
||||||
|
alert: () => import('@iconify-icons/mingcute/alert-line'),
|
||||||
};
|
};
|
||||||
|
|
||||||
function Icon({
|
function Icon({
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { useEffect, useRef, useState } from 'preact/hooks';
|
||||||
import { api } from '../utils/api';
|
import { api } from '../utils/api';
|
||||||
|
|
||||||
import Icon from './icon';
|
import Icon from './icon';
|
||||||
|
import MenuConfirm from './menu-confirm';
|
||||||
|
|
||||||
function ListAddEdit({ list, onClose }) {
|
function ListAddEdit({ list, onClose }) {
|
||||||
const { masto } = api();
|
const { masto } = api();
|
||||||
|
@ -103,13 +104,14 @@ function ListAddEdit({ list, onClose }) {
|
||||||
{editMode ? 'Save' : 'Create'}
|
{editMode ? 'Save' : 'Create'}
|
||||||
</button>
|
</button>
|
||||||
{editMode && (
|
{editMode && (
|
||||||
<button
|
<MenuConfirm
|
||||||
type="button"
|
|
||||||
class="light danger"
|
|
||||||
disabled={uiState === 'loading'}
|
disabled={uiState === 'loading'}
|
||||||
|
align="end"
|
||||||
|
menuItemClassName="danger"
|
||||||
|
confirmLabel="Delete this list?"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const yes = confirm('Delete this list?');
|
// const yes = confirm('Delete this list?');
|
||||||
if (!yes) return;
|
// if (!yes) return;
|
||||||
setUiState('loading');
|
setUiState('loading');
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
|
@ -127,8 +129,14 @@ function ListAddEdit({ list, onClose }) {
|
||||||
})();
|
})();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Delete…
|
<button
|
||||||
</button>
|
type="button"
|
||||||
|
class="light danger"
|
||||||
|
disabled={uiState === 'loading'}
|
||||||
|
>
|
||||||
|
Delete…
|
||||||
|
</button>
|
||||||
|
</MenuConfirm>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
43
src/components/menu-confirm.jsx
Normal file
43
src/components/menu-confirm.jsx
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import { Menu, MenuItem, SubMenu } from '@szhsin/react-menu';
|
||||||
|
import { cloneElement } from 'preact';
|
||||||
|
|
||||||
|
function MenuConfirm({
|
||||||
|
subMenu = false,
|
||||||
|
confirm = true,
|
||||||
|
confirmLabel,
|
||||||
|
menuItemClassName,
|
||||||
|
menuFooter,
|
||||||
|
...props
|
||||||
|
}) {
|
||||||
|
const { children, onClick, ...restProps } = props;
|
||||||
|
if (!confirm) {
|
||||||
|
if (subMenu) return <MenuItem {...props} />;
|
||||||
|
if (onClick) {
|
||||||
|
return cloneElement(children, {
|
||||||
|
onClick,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
const Parent = subMenu ? SubMenu : Menu;
|
||||||
|
return (
|
||||||
|
<Parent
|
||||||
|
openTrigger="clickOnly"
|
||||||
|
direction="bottom"
|
||||||
|
overflow="auto"
|
||||||
|
gap={-8}
|
||||||
|
shift={8}
|
||||||
|
menuClassName="menu-emphasized"
|
||||||
|
{...restProps}
|
||||||
|
menuButton={subMenu ? undefined : children}
|
||||||
|
label={subMenu ? children : undefined}
|
||||||
|
>
|
||||||
|
<MenuItem className={menuItemClassName} onClick={onClick}>
|
||||||
|
{confirmLabel}
|
||||||
|
</MenuItem>
|
||||||
|
{menuFooter}
|
||||||
|
</Parent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MenuConfirm;
|
|
@ -28,6 +28,7 @@ import { snapshot } from 'valtio/vanilla';
|
||||||
import AccountBlock from '../components/account-block';
|
import AccountBlock from '../components/account-block';
|
||||||
import EmojiText from '../components/emoji-text';
|
import EmojiText from '../components/emoji-text';
|
||||||
import Loader from '../components/loader';
|
import Loader from '../components/loader';
|
||||||
|
import MenuConfirm from '../components/menu-confirm';
|
||||||
import Modal from '../components/modal';
|
import Modal from '../components/modal';
|
||||||
import NameText from '../components/name-text';
|
import NameText from '../components/name-text';
|
||||||
import Poll from '../components/poll';
|
import Poll from '../components/poll';
|
||||||
|
@ -325,6 +326,12 @@ function Status({
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Check if media has no descriptions
|
||||||
|
const mediaNoDesc = useMemo(() => {
|
||||||
|
return mediaAttachments.some(
|
||||||
|
(attachment) => !attachment.description?.trim?.(),
|
||||||
|
);
|
||||||
|
}, [mediaAttachments]);
|
||||||
const boostStatus = async () => {
|
const boostStatus = async () => {
|
||||||
if (!sameInstance || !authenticated) {
|
if (!sameInstance || !authenticated) {
|
||||||
alert(unauthInteractionErrorMessage);
|
alert(unauthInteractionErrorMessage);
|
||||||
|
@ -332,12 +339,8 @@ function Status({
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if (!reblogged) {
|
if (!reblogged) {
|
||||||
// Check if media has no descriptions
|
|
||||||
const hasNoDescriptions = mediaAttachments.some(
|
|
||||||
(attachment) => !attachment.description?.trim?.(),
|
|
||||||
);
|
|
||||||
let confirmText = 'Boost this post?';
|
let confirmText = 'Boost this post?';
|
||||||
if (hasNoDescriptions) {
|
if (mediaNoDesc) {
|
||||||
confirmText += '\n\n⚠️ Some media have no descriptions.';
|
confirmText += '\n\n⚠️ Some media have no descriptions.';
|
||||||
}
|
}
|
||||||
const yes = confirm(confirmText);
|
const yes = confirm(confirmText);
|
||||||
|
@ -367,6 +370,34 @@ function Status({
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const confirmBoostStatus = async () => {
|
||||||
|
if (!sameInstance || !authenticated) {
|
||||||
|
alert(unauthInteractionErrorMessage);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// Optimistic
|
||||||
|
states.statuses[sKey] = {
|
||||||
|
...status,
|
||||||
|
reblogged: !reblogged,
|
||||||
|
reblogsCount: reblogsCount + (reblogged ? -1 : 1),
|
||||||
|
};
|
||||||
|
if (reblogged) {
|
||||||
|
const newStatus = await masto.v1.statuses.unreblog(id);
|
||||||
|
saveStatus(newStatus, instance);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
const newStatus = await masto.v1.statuses.reblog(id);
|
||||||
|
saveStatus(newStatus, instance);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
// Revert optimistism
|
||||||
|
states.statuses[sKey] = status;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const favouriteStatus = async () => {
|
const favouriteStatus = async () => {
|
||||||
if (!sameInstance || !authenticated) {
|
if (!sameInstance || !authenticated) {
|
||||||
|
@ -490,11 +521,27 @@ function Status({
|
||||||
{!isSizeLarge && sameInstance && (
|
{!isSizeLarge && sameInstance && (
|
||||||
<>
|
<>
|
||||||
<div class="menu-horizontal">
|
<div class="menu-horizontal">
|
||||||
<MenuItem
|
<MenuConfirm
|
||||||
|
subMenu
|
||||||
|
confirmLabel={
|
||||||
|
<>
|
||||||
|
<Icon icon="rocket" />
|
||||||
|
<span>Unboost?</span>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
menuFooter={
|
||||||
|
mediaNoDesc &&
|
||||||
|
!reblogged && (
|
||||||
|
<div class="footer">
|
||||||
|
<Icon icon="alert" />
|
||||||
|
Some media have no descriptions.
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
disabled={!canBoost}
|
disabled={!canBoost}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
try {
|
try {
|
||||||
const done = await boostStatus();
|
const done = await confirmBoostStatus();
|
||||||
if (!isSizeLarge && done) {
|
if (!isSizeLarge && done) {
|
||||||
showToast(reblogged ? 'Unboosted' : 'Boosted');
|
showToast(reblogged ? 'Unboosted' : 'Boosted');
|
||||||
}
|
}
|
||||||
|
@ -508,7 +555,7 @@ function Status({
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<span>{reblogged ? 'Unboost' : 'Boost…'}</span>
|
<span>{reblogged ? 'Unboost' : 'Boost…'}</span>
|
||||||
</MenuItem>
|
</MenuConfirm>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
try {
|
try {
|
||||||
|
@ -660,27 +707,35 @@ function Status({
|
||||||
<span>Edit</span>
|
<span>Edit</span>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{isSizeLarge && (
|
{isSizeLarge && (
|
||||||
<MenuItem
|
<MenuConfirm
|
||||||
|
subMenu
|
||||||
|
confirmLabel={
|
||||||
|
<>
|
||||||
|
<Icon icon="trash" />
|
||||||
|
<span>Delete this post?</span>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
menuItemClassName="danger"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const yes = confirm('Delete this post?');
|
// const yes = confirm('Delete this post?');
|
||||||
if (yes) {
|
// if (yes) {
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
await masto.v1.statuses.remove(id);
|
await masto.v1.statuses.remove(id);
|
||||||
const cachedStatus = getStatus(id, instance);
|
const cachedStatus = getStatus(id, instance);
|
||||||
cachedStatus._deleted = true;
|
cachedStatus._deleted = true;
|
||||||
showToast('Deleted');
|
showToast('Deleted');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
showToast('Unable to delete');
|
showToast('Unable to delete');
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}
|
// }
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon icon="trash" />
|
<Icon icon="trash" />
|
||||||
<span>Delete…</span>
|
<span>Delete…</span>
|
||||||
</MenuItem>
|
</MenuConfirm>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -1157,7 +1212,7 @@ function Status({
|
||||||
onClick={replyStatus}
|
onClick={replyStatus}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="action has-count">
|
{/* <div class="action has-count">
|
||||||
<StatusButton
|
<StatusButton
|
||||||
checked={reblogged}
|
checked={reblogged}
|
||||||
title={['Boost', 'Unboost']}
|
title={['Boost', 'Unboost']}
|
||||||
|
@ -1168,7 +1223,45 @@ function Status({
|
||||||
onClick={boostStatus}
|
onClick={boostStatus}
|
||||||
disabled={!canBoost}
|
disabled={!canBoost}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div> */}
|
||||||
|
<Menu
|
||||||
|
portal={{
|
||||||
|
target:
|
||||||
|
document.querySelector('.status-deck') || document.body,
|
||||||
|
}}
|
||||||
|
align="start"
|
||||||
|
gap={4}
|
||||||
|
overflow="auto"
|
||||||
|
viewScroll="close"
|
||||||
|
boundingBoxPadding="8 8 8 8"
|
||||||
|
shift={-8}
|
||||||
|
menuClassName="menu-emphasized"
|
||||||
|
menuButton={({ open }) => (
|
||||||
|
<div class="action has-count">
|
||||||
|
<StatusButton
|
||||||
|
checked={reblogged}
|
||||||
|
title={['Boost', 'Unboost']}
|
||||||
|
alt={['Boost', 'Boosted']}
|
||||||
|
class="reblog-button"
|
||||||
|
icon="rocket"
|
||||||
|
count={reblogsCount}
|
||||||
|
// onClick={boostStatus}
|
||||||
|
disabled={open || !canBoost}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<MenuItem onClick={confirmBoostStatus}>
|
||||||
|
<Icon icon="rocket" />
|
||||||
|
<span>Boost to everyone?</span>
|
||||||
|
</MenuItem>
|
||||||
|
{mediaNoDesc && (
|
||||||
|
<div class="footer">
|
||||||
|
<Icon icon="alert" />
|
||||||
|
Some media have no descriptions.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Menu>
|
||||||
<div class="action has-count">
|
<div class="action has-count">
|
||||||
<StatusButton
|
<StatusButton
|
||||||
checked={favourited}
|
checked={favourited}
|
||||||
|
@ -1682,6 +1775,7 @@ function StatusButton({
|
||||||
title={buttonTitle}
|
title={buttonTitle}
|
||||||
class={`plain ${className} ${checked ? 'checked' : ''}`}
|
class={`plain ${className} ${checked ? 'checked' : ''}`}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
|
if (!onClick) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onClick(e);
|
onClick(e);
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { useReducer, useState } from 'preact/hooks';
|
||||||
import Avatar from '../components/avatar';
|
import Avatar from '../components/avatar';
|
||||||
import Icon from '../components/icon';
|
import Icon from '../components/icon';
|
||||||
import Link from '../components/link';
|
import Link from '../components/link';
|
||||||
|
import MenuConfirm from '../components/menu-confirm';
|
||||||
import NameText from '../components/name-text';
|
import NameText from '../components/name-text';
|
||||||
import { api } from '../utils/api';
|
import { api } from '../utils/api';
|
||||||
import states from '../utils/states';
|
import states from '../utils/states';
|
||||||
|
@ -126,11 +127,19 @@ function Accounts({ onClose }) {
|
||||||
<span>Set as default</span>
|
<span>Set as default</span>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
<MenuItem
|
<MenuConfirm
|
||||||
|
subMenu
|
||||||
|
confirmLabel={
|
||||||
|
<>
|
||||||
|
<Icon icon="exit" />
|
||||||
|
<span>Log out @{account.info.acct}?</span>
|
||||||
|
</>
|
||||||
|
}
|
||||||
disabled={!isCurrent}
|
disabled={!isCurrent}
|
||||||
|
menuItemClassName="danger"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const yes = confirm('Log out?');
|
// const yes = confirm('Log out?');
|
||||||
if (!yes) return;
|
// if (!yes) return;
|
||||||
accounts.splice(i, 1);
|
accounts.splice(i, 1);
|
||||||
store.local.setJSON('accounts', accounts);
|
store.local.setJSON('accounts', accounts);
|
||||||
// location.reload();
|
// location.reload();
|
||||||
|
@ -139,7 +148,7 @@ function Accounts({ onClose }) {
|
||||||
>
|
>
|
||||||
<Icon icon="exit" />
|
<Icon icon="exit" />
|
||||||
<span>Log out…</span>
|
<span>Log out…</span>
|
||||||
</MenuItem>
|
</MenuConfirm>
|
||||||
</Menu>
|
</Menu>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { useNavigate, useParams } from 'react-router-dom';
|
||||||
|
|
||||||
import Icon from '../components/icon';
|
import Icon from '../components/icon';
|
||||||
import Menu2 from '../components/menu2';
|
import Menu2 from '../components/menu2';
|
||||||
|
import MenuConfirm from '../components/menu-confirm';
|
||||||
import Timeline from '../components/timeline';
|
import Timeline from '../components/timeline';
|
||||||
import { api } from '../utils/api';
|
import { api } from '../utils/api';
|
||||||
import showToast from '../utils/show-toast';
|
import showToast from '../utils/show-toast';
|
||||||
|
@ -149,16 +150,19 @@ function Hashtags({ columnMode, ...props }) {
|
||||||
>
|
>
|
||||||
{!!info && hashtags.length === 1 && (
|
{!!info && hashtags.length === 1 && (
|
||||||
<>
|
<>
|
||||||
<MenuItem
|
<MenuConfirm
|
||||||
|
subMenu
|
||||||
|
confirm={info.following}
|
||||||
|
confirmLabel={`Unfollow #${hashtag}?`}
|
||||||
disabled={followUIState === 'loading' || !authenticated}
|
disabled={followUIState === 'loading' || !authenticated}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setFollowUIState('loading');
|
setFollowUIState('loading');
|
||||||
if (info.following) {
|
if (info.following) {
|
||||||
const yes = confirm(`Unfollow #${hashtag}?`);
|
// const yes = confirm(`Unfollow #${hashtag}?`);
|
||||||
if (!yes) {
|
// if (!yes) {
|
||||||
setFollowUIState('default');
|
// setFollowUIState('default');
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
masto.v1.tags
|
masto.v1.tags
|
||||||
.unfollow(hashtag)
|
.unfollow(hashtag)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
@ -198,7 +202,7 @@ function Hashtags({ columnMode, ...props }) {
|
||||||
<Icon icon="plus" /> <span>Follow</span>
|
<Icon icon="plus" /> <span>Follow</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</MenuItem>
|
</MenuConfirm>
|
||||||
<MenuDivider />
|
<MenuDivider />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import Icon from '../components/icon';
|
||||||
import Link from '../components/link';
|
import Link from '../components/link';
|
||||||
import ListAddEdit from '../components/list-add-edit';
|
import ListAddEdit from '../components/list-add-edit';
|
||||||
import Menu2 from '../components/menu2';
|
import Menu2 from '../components/menu2';
|
||||||
|
import MenuConfirm from '../components/menu-confirm';
|
||||||
import Modal from '../components/modal';
|
import Modal from '../components/modal';
|
||||||
import Timeline from '../components/timeline';
|
import Timeline from '../components/timeline';
|
||||||
import { api } from '../utils/api';
|
import { api } from '../utils/api';
|
||||||
|
@ -263,10 +264,11 @@ function RemoveAddButton({ account, listID }) {
|
||||||
const [removed, setRemoved] = useState(false);
|
const [removed, setRemoved] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<MenuConfirm
|
||||||
type="button"
|
confirm={!removed}
|
||||||
class={`light ${removed ? '' : 'danger'}`}
|
confirmLabel={<span>Remove @{account.username} from list?</span>}
|
||||||
disabled={uiState === 'loading'}
|
align="end"
|
||||||
|
menuItemClassName="danger"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (removed) {
|
if (removed) {
|
||||||
setUIState('loading');
|
setUIState('loading');
|
||||||
|
@ -282,8 +284,8 @@ function RemoveAddButton({ account, listID }) {
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
} else {
|
} else {
|
||||||
const yes = confirm(`Remove ${account.username} from this list?`);
|
// const yes = confirm(`Remove ${account.username} from this list?`);
|
||||||
if (!yes) return;
|
// if (!yes) return;
|
||||||
setUIState('loading');
|
setUIState('loading');
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
|
@ -300,8 +302,14 @@ function RemoveAddButton({ account, listID }) {
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{removed ? 'Add' : 'Remove…'}
|
<button
|
||||||
</button>
|
type="button"
|
||||||
|
class={`light ${removed ? '' : 'danger'}`}
|
||||||
|
disabled={uiState === 'loading'}
|
||||||
|
>
|
||||||
|
{removed ? 'Add' : 'Remove…'}
|
||||||
|
</button>
|
||||||
|
</MenuConfirm>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
34
src/utils/toast-alert.js
Normal file
34
src/utils/toast-alert.js
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
// Replace alert() with toastify-js
|
||||||
|
import Toastify from 'toastify-js';
|
||||||
|
|
||||||
|
const nativeAlert = window.alert;
|
||||||
|
if (!window.__nativeAlert) window.__nativeAlert = nativeAlert;
|
||||||
|
|
||||||
|
window.alert = function (message) {
|
||||||
|
console.debug(
|
||||||
|
'ALERT: This is a custom alert() function. Native alert() is still available as window.__nativeAlert()',
|
||||||
|
);
|
||||||
|
// If Error object, show the message
|
||||||
|
if (message instanceof Error && message?.message) {
|
||||||
|
message = message.message;
|
||||||
|
}
|
||||||
|
// If not string, stringify it
|
||||||
|
if (typeof message !== 'string') {
|
||||||
|
message = JSON.stringify(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
const toast = Toastify({
|
||||||
|
text: message,
|
||||||
|
className: 'alert',
|
||||||
|
gravity: 'top',
|
||||||
|
position: 'center',
|
||||||
|
duration: 10_000,
|
||||||
|
offset: {
|
||||||
|
y: 48,
|
||||||
|
},
|
||||||
|
onClick: () => {
|
||||||
|
toast.hideToast();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
toast.showToast();
|
||||||
|
};
|
Loading…
Reference in a new issue