Compare commits
11 commits
4dd38b6644
...
8cc8e03d66
Author | SHA1 | Date | |
---|---|---|---|
Alexander Yakovlev | 8cc8e03d66 | ||
707b51a1a0 | |||
201ca6ce4a | |||
a419bb9b61 | |||
a8b5c8cd64 | |||
a3236ea0f0 | |||
c595b0ee31 | |||
89f34d7942 | |||
f23e4b0dd9 | |||
e7d2d088ba | |||
bf609b979e |
8
package-lock.json
generated
8
package-lock.json
generated
|
@ -23,7 +23,7 @@
|
|||
"idb-keyval": "~6.2.1",
|
||||
"just-debounce-it": "~3.2.0",
|
||||
"lz-string": "~1.5.0",
|
||||
"masto": "~6.5.4",
|
||||
"masto": "~6.6.0",
|
||||
"moize": "~6.1.6",
|
||||
"p-retry": "~6.2.0",
|
||||
"p-throttle": "~6.1.0",
|
||||
|
@ -5622,9 +5622,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/masto": {
|
||||
"version": "6.5.4",
|
||||
"resolved": "https://registry.npmjs.org/masto/-/masto-6.5.4.tgz",
|
||||
"integrity": "sha512-hJbgQ6RlWpeA5FJ+l0CPha2zq+OlWr7bbUn09xnzNNOvBVgusTwwB898n996D3qhhr3jEpR36QUpZVxnxH+P7g==",
|
||||
"version": "6.6.0",
|
||||
"resolved": "https://registry.npmjs.org/masto/-/masto-6.6.0.tgz",
|
||||
"integrity": "sha512-spXDwI22M7cWaFPBmpEY+05F+9kAE0nLwzUx5YPECKsLEEQ+c+a92yo/WOnocNs/ILr9/OCKjYPyCykL6TVw+w==",
|
||||
"dependencies": {
|
||||
"change-case": "^4.1.2",
|
||||
"events-to-async": "^2.0.1",
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
"idb-keyval": "~6.2.1",
|
||||
"just-debounce-it": "~3.2.0",
|
||||
"lz-string": "~1.5.0",
|
||||
"masto": "~6.5.4",
|
||||
"masto": "~6.6.0",
|
||||
"moize": "~6.1.6",
|
||||
"p-retry": "~6.2.0",
|
||||
"p-throttle": "~6.1.0",
|
||||
|
|
|
@ -103,6 +103,10 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
|
|||
max-width: 100%;
|
||||
background-color: var(--bg-color);
|
||||
overflow-anchor: auto;
|
||||
|
||||
&.wide {
|
||||
width: 60em;
|
||||
}
|
||||
}
|
||||
.deck.contained {
|
||||
overflow: auto;
|
||||
|
@ -2109,7 +2113,7 @@ meter.donut[hidden] {
|
|||
:root .toastify {
|
||||
user-select: none;
|
||||
padding: 8px 16px;
|
||||
border-radius: 999px;
|
||||
border-radius: 44px;
|
||||
pointer-events: none;
|
||||
color: var(--button-text-color);
|
||||
text-shadow: 0 calc(var(--hairline-width) * -1) var(--drop-shadow-color);
|
||||
|
|
|
@ -24,6 +24,7 @@ import Shortcuts from './components/shortcuts';
|
|||
import NotFound from './pages/404';
|
||||
import AccountStatuses from './pages/account-statuses';
|
||||
import Bookmarks from './pages/bookmarks';
|
||||
import Catchup from './pages/catchup';
|
||||
import Favourites from './pages/favourites';
|
||||
import FollowedHashtags from './pages/followed-hashtags';
|
||||
import Following from './pages/following';
|
||||
|
@ -394,7 +395,7 @@ function PrimaryRoutes({ isLoggedIn, loading }) {
|
|||
const location = useLocation();
|
||||
const nonRootLocation = useMemo(() => {
|
||||
const { pathname } = location;
|
||||
return !/^\/(login|welcome)/.test(pathname);
|
||||
return !/^\/(login|welcome)/i.test(pathname);
|
||||
}, [location]);
|
||||
|
||||
return (
|
||||
|
@ -457,6 +458,7 @@ function SecondaryRoutes({ isLoggedIn }) {
|
|||
<Route path=":id" element={<List />} />
|
||||
</Route>
|
||||
<Route path="/ft" element={<FollowedHashtags />} />
|
||||
<Route path="/catchup" element={<Catchup />} />
|
||||
</>
|
||||
)}
|
||||
<Route path="/:instance?/t/:hashtag" element={<Hashtag />} />
|
||||
|
|
|
@ -12,7 +12,8 @@ body.cloak,
|
|||
.account-container :is(header, main > *:not(.actions)),
|
||||
.account-container :is(header, main > *:not(.actions)) *,
|
||||
.header-double-lines,
|
||||
.account-block {
|
||||
.account-block,
|
||||
.post-peek-html * {
|
||||
text-decoration-thickness: 1.1em;
|
||||
text-decoration-line: line-through;
|
||||
/* text-rendering: optimizeSpeed; */
|
||||
|
@ -26,9 +27,10 @@ body.cloak,
|
|||
|
||||
.status :is(img, video, audio),
|
||||
.media-post .media,
|
||||
.avatar,
|
||||
.avatar *,
|
||||
.emoji,
|
||||
.header-banner {
|
||||
.header-banner,
|
||||
.post-peek-media {
|
||||
filter: contrast(0) !important;
|
||||
background-color: #000 !important;
|
||||
}
|
||||
|
|
|
@ -73,7 +73,7 @@ export const ICONS = {
|
|||
() => import('@iconify-icons/mingcute/forbid-circle-line'),
|
||||
'180deg',
|
||||
],
|
||||
flag: () => import('@iconify-icons/mingcute/flag-4-line'),
|
||||
flag: () => import('@iconify-icons/mingcute/flag-1-line'),
|
||||
time: () => import('@iconify-icons/mingcute/time-line'),
|
||||
refresh: () => import('@iconify-icons/mingcute/refresh-2-line'),
|
||||
emoji2: () => import('@iconify-icons/mingcute/emoji-2-line'),
|
||||
|
|
|
@ -9,11 +9,15 @@
|
|||
--original-color: var(--link-color);
|
||||
|
||||
.note {
|
||||
font-size: 95%;
|
||||
font-size: 0.95em;
|
||||
line-height: 1.4;
|
||||
text-wrap: pretty;
|
||||
margin-bottom: 16px;
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
> *:first-child {
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
|
|
|
@ -1238,10 +1238,38 @@ function RelatedActions({
|
|||
</>
|
||||
)}
|
||||
</MenuConfirm>
|
||||
{/* <MenuItem>
|
||||
<Icon icon="flag" />
|
||||
<span>Report @{username}…</span>
|
||||
</MenuItem> */}
|
||||
<MenuItem
|
||||
className="danger"
|
||||
onClick={() => {
|
||||
states.showReportModal = {
|
||||
account: currentInfo || info,
|
||||
};
|
||||
}}
|
||||
>
|
||||
<Icon icon="flag" />
|
||||
<span>Report @{username}…</span>
|
||||
</MenuItem>
|
||||
</>
|
||||
)}
|
||||
{import.meta.env.DEV && currentAuthenticated && isSelf && (
|
||||
<>
|
||||
<MenuDivider />
|
||||
<MenuItem
|
||||
onClick={async () => {
|
||||
const relationships =
|
||||
await currentMasto.v1.accounts.relationships.fetch({
|
||||
id: [accountID.current],
|
||||
});
|
||||
const { note } = relationships[0] || {};
|
||||
if (note) {
|
||||
alert(note);
|
||||
console.log(note);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Icon icon="pencil" />
|
||||
<span>See note</span>
|
||||
</MenuItem>
|
||||
</>
|
||||
)}
|
||||
</Menu2>
|
||||
|
|
|
@ -1558,7 +1558,7 @@ const Textarea = forwardRef((props, ref) => {
|
|||
onKeyDown={(e) => {
|
||||
// Get line before cursor position after pressing 'Enter'
|
||||
const { key, target } = e;
|
||||
if (key === 'Enter') {
|
||||
if (key === 'Enter' && !(e.ctrlKey || e.metaKey)) {
|
||||
try {
|
||||
const { value, selectionStart } = target;
|
||||
const textBeforeCursor = value.slice(0, selectionStart);
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
text-shadow: 0 1px var(--bg-blur-color);
|
||||
transition: opacity 0.3s ease-out;
|
||||
|
||||
&:not(#columns &) {
|
||||
#trending-page &:not(#columns &) {
|
||||
@media (min-width: 40em) {
|
||||
width: 95vw;
|
||||
max-width: calc(320px * 3.3);
|
||||
|
@ -96,6 +96,7 @@
|
|||
}
|
||||
|
||||
article {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
|
@ -113,34 +114,34 @@
|
|||
margin: 0 0 -16px;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
img {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
vertical-align: top;
|
||||
mask-image: linear-gradient(
|
||||
to bottom,
|
||||
hsl(0, 0%, 0%) 0%,
|
||||
hsla(0, 0%, 0%, 0.987) 14%,
|
||||
hsla(0, 0%, 0%, 0.951) 26.2%,
|
||||
hsla(0, 0%, 0%, 0.896) 36.8%,
|
||||
hsla(0, 0%, 0%, 0.825) 45.9%,
|
||||
hsla(0, 0%, 0%, 0.741) 53.7%,
|
||||
hsla(0, 0%, 0%, 0.648) 60.4%,
|
||||
hsla(0, 0%, 0%, 0.55) 66.2%,
|
||||
hsla(0, 0%, 0%, 0.45) 71.2%,
|
||||
hsla(0, 0%, 0%, 0.352) 75.6%,
|
||||
hsla(0, 0%, 0%, 0.259) 79.6%,
|
||||
hsla(0, 0%, 0%, 0.175) 83.4%,
|
||||
hsla(0, 0%, 0%, 0.104) 87.2%,
|
||||
hsla(0, 0%, 0%, 0.049) 91.1%,
|
||||
hsla(0, 0%, 0%, 0.013) 95.3%,
|
||||
hsla(0, 0%, 0%, 0) 100%
|
||||
);
|
||||
img {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
vertical-align: top;
|
||||
mask-image: linear-gradient(
|
||||
to bottom,
|
||||
hsl(0, 0%, 0%) 0%,
|
||||
hsla(0, 0%, 0%, 0.987) 14%,
|
||||
hsla(0, 0%, 0%, 0.951) 26.2%,
|
||||
hsla(0, 0%, 0%, 0.896) 36.8%,
|
||||
hsla(0, 0%, 0%, 0.825) 45.9%,
|
||||
hsla(0, 0%, 0%, 0.741) 53.7%,
|
||||
hsla(0, 0%, 0%, 0.648) 60.4%,
|
||||
hsla(0, 0%, 0%, 0.55) 66.2%,
|
||||
hsla(0, 0%, 0%, 0.45) 71.2%,
|
||||
hsla(0, 0%, 0%, 0.352) 75.6%,
|
||||
hsla(0, 0%, 0%, 0.259) 79.6%,
|
||||
hsla(0, 0%, 0%, 0.175) 83.4%,
|
||||
hsla(0, 0%, 0%, 0.104) 87.2%,
|
||||
hsla(0, 0%, 0%, 0.049) 91.1%,
|
||||
hsla(0, 0%, 0%, 0.013) 95.3%,
|
||||
hsla(0, 0%, 0%, 0) 100%
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -187,5 +188,9 @@
|
|||
overflow: hidden;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 4px 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -56,8 +56,18 @@ function Modal({ children, onClose, onClick, class: className }) {
|
|||
}}
|
||||
tabIndex="-1"
|
||||
onFocus={(e) => {
|
||||
if (e.target === e.currentTarget) {
|
||||
modalRef.current?.querySelector?.('[tabindex="-1"]')?.focus?.();
|
||||
try {
|
||||
if (e.target === e.currentTarget) {
|
||||
const focusElement =
|
||||
modalRef.current?.querySelector('[tabindex="-1"]');
|
||||
const isFocusable =
|
||||
getComputedStyle(focusElement)?.pointerEvents !== 'none';
|
||||
if (focusElement && isFocusable) {
|
||||
focusElement.focus();
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
|
|
@ -15,6 +15,7 @@ import GenericAccounts from './generic-accounts';
|
|||
import MediaAltModal from './media-alt-modal';
|
||||
import MediaModal from './media-modal';
|
||||
import Modal from './modal';
|
||||
import ReportModal from './report-modal';
|
||||
import ShortcutsSettings from './shortcuts-settings';
|
||||
|
||||
subscribe(states, (changes) => {
|
||||
|
@ -218,6 +219,21 @@ export default function Modals() {
|
|||
/>
|
||||
</Modal>
|
||||
)}
|
||||
{!!snapStates.showReportModal && (
|
||||
<Modal
|
||||
onClose={() => {
|
||||
states.showReportModal = false;
|
||||
}}
|
||||
>
|
||||
<ReportModal
|
||||
account={snapStates.showReportModal.account}
|
||||
post={snapStates.showReportModal.post}
|
||||
onClose={() => {
|
||||
states.showReportModal = false;
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import './nav-menu.css';
|
||||
|
||||
import { ControlledMenu, MenuDivider, MenuItem } from '@szhsin/react-menu';
|
||||
import { memo } from 'preact/compat';
|
||||
import { useEffect, useRef, useState } from 'preact/hooks';
|
||||
import { useLongPress } from 'use-long-press';
|
||||
import { useSnapshot } from 'valtio';
|
||||
|
@ -83,6 +84,7 @@ function NavMenu(props) {
|
|||
return results;
|
||||
}
|
||||
|
||||
const buttonClickTS = useRef();
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
|
@ -93,6 +95,7 @@ function NavMenu(props) {
|
|||
} ${open ? 'active' : ''}`}
|
||||
style={{ position: 'relative' }}
|
||||
onClick={() => {
|
||||
buttonClickTS.current = Date.now();
|
||||
setMenuState((state) => (!state ? 'open' : undefined));
|
||||
}}
|
||||
onContextMenu={(e) => {
|
||||
|
@ -124,6 +127,9 @@ function NavMenu(props) {
|
|||
zIndex: 10,
|
||||
},
|
||||
onClick: () => {
|
||||
if (Date.now() - buttonClickTS.current < 300) {
|
||||
return;
|
||||
}
|
||||
setMenuState(undefined);
|
||||
},
|
||||
}}
|
||||
|
@ -170,6 +176,10 @@ function NavMenu(props) {
|
|||
<Icon icon="following" size="l" /> <span>Following</span>
|
||||
</MenuLink>
|
||||
)}
|
||||
<MenuLink to="/catchup">
|
||||
<Icon icon="history" />
|
||||
<span>Catch-up</span>
|
||||
</MenuLink>
|
||||
<MenuLink to="/mentions">
|
||||
<Icon icon="at" size="l" /> <span>Mentions</span>
|
||||
</MenuLink>
|
||||
|
@ -298,4 +308,4 @@ function NavMenu(props) {
|
|||
);
|
||||
}
|
||||
|
||||
export default NavMenu;
|
||||
export default memo(NavMenu);
|
||||
|
|
196
src/components/report-modal.css
Normal file
196
src/components/report-modal.css
Normal file
|
@ -0,0 +1,196 @@
|
|||
.report-modal-container {
|
||||
width: 100%;
|
||||
max-height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 40em;
|
||||
background-color: var(--bg-color);
|
||||
box-shadow: 0 16px 32px -8px var(--drop-shadow-color);
|
||||
overflow-y: auto;
|
||||
animation: slide-up-smooth 0.3s ease-in-out;
|
||||
position: relative;
|
||||
|
||||
@media (min-width: 40em) {
|
||||
max-height: calc(100% - 32px);
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.top-controls {
|
||||
position: sticky;
|
||||
top: var(--sai-top, 0);
|
||||
z-index: 1;
|
||||
background-color: var(--bg-blur-color);
|
||||
backdrop-filter: blur(16px);
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
justify-content: space-between;
|
||||
pointer-events: auto;
|
||||
align-items: center;
|
||||
|
||||
h1 {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
main {
|
||||
padding: 0 16px 16px;
|
||||
/* display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px; */
|
||||
}
|
||||
|
||||
form {
|
||||
/* display: flex; */
|
||||
/* flex-direction: column; */
|
||||
/* gap: 16px; */
|
||||
text-wrap: pretty;
|
||||
|
||||
input {
|
||||
margin-inline: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.report-preview {
|
||||
background-color: var(--bg-color);
|
||||
border-radius: 8px;
|
||||
border: 2px dashed var(--red-color);
|
||||
box-shadow: inset 0 0 16px -4px var(--red-bg-color);
|
||||
overflow: auto;
|
||||
max-height: 33vh;
|
||||
|
||||
.status {
|
||||
font-size: 90%;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-drag: none;
|
||||
filter: grayscale(0.5);
|
||||
}
|
||||
|
||||
.account-block {
|
||||
margin: 16px;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-drag: none;
|
||||
filter: grayscale(0.5);
|
||||
}
|
||||
}
|
||||
|
||||
.rubber-stamp {
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
position: absolute;
|
||||
right: 32px;
|
||||
margin-top: -48px;
|
||||
animation: rubber-stamp 0.3s ease-in both;
|
||||
position: absolute;
|
||||
font-weight: bold;
|
||||
color: var(--red-color);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: -0.5px;
|
||||
font-size: 2em;
|
||||
line-height: 1;
|
||||
padding: 0.1em;
|
||||
border: 0.15em solid var(--red-color);
|
||||
border-radius: 0.3em;
|
||||
background-color: var(--bg-blur-color);
|
||||
text-align: center;
|
||||
/* Noise pattern - https://css-tricks.com/making-static-noise-from-a-weird-css-gradient-bug/ */
|
||||
mask-image: repeating-conic-gradient(
|
||||
#000 0 0.01%,
|
||||
rgba(0, 0, 0, 0.45) 0 0.02%
|
||||
);
|
||||
|
||||
small {
|
||||
display: block;
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
margin-block: 0.5em;
|
||||
}
|
||||
|
||||
section {
|
||||
label {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
margin-bottom: 8px;
|
||||
|
||||
&:has(:checked) {
|
||||
.insignificant {
|
||||
color: var(--text-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
> label:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.report-categories {
|
||||
label {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.report-rules {
|
||||
margin-left: 1.75em;
|
||||
}
|
||||
}
|
||||
|
||||
.report-comment {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: flex-start;
|
||||
margin-top: 2em;
|
||||
flex-wrap: wrap;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
padding: 8px 0 0;
|
||||
flex-shrink: 0;
|
||||
|
||||
label {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
textarea {
|
||||
flex-grow: 1;
|
||||
resize: vertical;
|
||||
}
|
||||
}
|
||||
|
||||
footer {
|
||||
margin-top: 2em;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
|
||||
button {
|
||||
border-radius: 8px !important;
|
||||
align-self: stretch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes rubber-stamp {
|
||||
0% {
|
||||
transform: rotate(-20deg) scale(5);
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
transform: rotate(-20deg) scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
298
src/components/report-modal.jsx
Normal file
298
src/components/report-modal.jsx
Normal file
|
@ -0,0 +1,298 @@
|
|||
import './report-modal.css';
|
||||
|
||||
import { Fragment } from 'preact';
|
||||
import { useMemo, useRef, useState } from 'preact/hooks';
|
||||
|
||||
import { api } from '../utils/api';
|
||||
import showToast from '../utils/show-toast';
|
||||
import { getCurrentInstance } from '../utils/store-utils';
|
||||
|
||||
import AccountBlock from './account-block';
|
||||
import Icon from './icon';
|
||||
import Loader from './loader';
|
||||
import Status from './status';
|
||||
|
||||
// NOTE: `dislike` hidden for now, it's actually not used for reporting
|
||||
// Mastodon shows another screen for unfollowing, muting or blocking instead of reporting
|
||||
|
||||
const CATEGORIES = [, /*'dislike'*/ 'spam', 'legal', 'violation', 'other'];
|
||||
// `violation` will be set if there are `rule_ids[]`
|
||||
|
||||
const CATEGORIES_INFO = {
|
||||
// dislike: {
|
||||
// label: 'Dislike',
|
||||
// description: 'Not something you want to see',
|
||||
// },
|
||||
spam: {
|
||||
label: 'Spam',
|
||||
description: 'Malicious links, fake engagement, or repetitive replies',
|
||||
},
|
||||
legal: {
|
||||
label: 'Illegal',
|
||||
description: "Violates the law of your or the server's country",
|
||||
},
|
||||
violation: {
|
||||
label: 'Server rule violation',
|
||||
description: 'Breaks specific server rules',
|
||||
stampLabel: 'Violation',
|
||||
},
|
||||
other: {
|
||||
label: 'Other',
|
||||
description: "Issue doesn't fit other categories",
|
||||
excludeStamp: true,
|
||||
},
|
||||
};
|
||||
|
||||
function ReportModal({ account, post, onClose }) {
|
||||
const { masto } = api();
|
||||
const [uiState, setUIState] = useState('default');
|
||||
const [username, domain] = account.acct.split('@');
|
||||
|
||||
const [rules, currentDomain] = useMemo(() => {
|
||||
const { rules, domain } = getCurrentInstance();
|
||||
return [rules || [], domain];
|
||||
});
|
||||
|
||||
const [selectedCategory, setSelectedCategory] = useState(null);
|
||||
const [showRules, setShowRules] = useState(false);
|
||||
|
||||
const rulesRef = useRef(null);
|
||||
const [hasRules, setHasRules] = useState(false);
|
||||
|
||||
return (
|
||||
<div class="report-modal-container">
|
||||
<div class="top-controls">
|
||||
<h1>{post ? 'Report Post' : `Report @${username}`}</h1>
|
||||
<button
|
||||
type="button"
|
||||
class="plain4 small"
|
||||
disabled={uiState === 'loading'}
|
||||
onClick={() => onClose()}
|
||||
>
|
||||
<Icon icon="x" size="xl" />
|
||||
</button>
|
||||
</div>
|
||||
<main>
|
||||
<div class="report-preview">
|
||||
{post ? (
|
||||
<Status status={post} size="s" previewMode />
|
||||
) : (
|
||||
<AccountBlock
|
||||
account={account}
|
||||
avatarSize="xxl"
|
||||
useAvatarStatic
|
||||
showStats
|
||||
showActivity
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{!!selectedCategory &&
|
||||
!CATEGORIES_INFO[selectedCategory].excludeStamp && (
|
||||
<span
|
||||
class="rubber-stamp"
|
||||
key={selectedCategory}
|
||||
aria-hidden="true"
|
||||
>
|
||||
{CATEGORIES_INFO[selectedCategory].stampLabel ||
|
||||
CATEGORIES_INFO[selectedCategory].label}
|
||||
<small>Pending review</small>
|
||||
</span>
|
||||
)}
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const formData = new FormData(e.target);
|
||||
const entries = Object.fromEntries(formData.entries());
|
||||
console.log('ENTRIES', entries);
|
||||
|
||||
let { category, comment, forward } = entries;
|
||||
if (!comment) comment = undefined;
|
||||
if (forward === 'on') forward = true;
|
||||
const ruleIds =
|
||||
category === 'violation'
|
||||
? Object.entries(entries)
|
||||
.filter(([key]) => key.startsWith('rule_ids'))
|
||||
.map(([key, value]) => value)
|
||||
: undefined;
|
||||
|
||||
const params = {
|
||||
category,
|
||||
comment,
|
||||
forward,
|
||||
ruleIds,
|
||||
};
|
||||
console.log('PARAMS', params);
|
||||
|
||||
setUIState('loading');
|
||||
(async () => {
|
||||
try {
|
||||
await masto.v1.reports.create({
|
||||
accountId: account.id,
|
||||
statusIds: post?.id ? [post.id] : undefined,
|
||||
category,
|
||||
comment,
|
||||
ruleIds,
|
||||
forward,
|
||||
});
|
||||
setUIState('success');
|
||||
showToast(post ? 'Post reported' : 'Profile reported');
|
||||
onClose();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
setUIState('error');
|
||||
showToast(
|
||||
error?.message ||
|
||||
(post
|
||||
? 'Unable to report post'
|
||||
: 'Unable to report profile'),
|
||||
);
|
||||
}
|
||||
})();
|
||||
}}
|
||||
>
|
||||
<p>
|
||||
{post
|
||||
? `What's the issue with this post?`
|
||||
: `What's the issue with this profile?`}
|
||||
</p>
|
||||
<section class="report-categories">
|
||||
{CATEGORIES.map((category) =>
|
||||
category === 'violation' && !rules?.length ? null : (
|
||||
<Fragment key={category}>
|
||||
<label class="report-category">
|
||||
<input
|
||||
type="radio"
|
||||
name="category"
|
||||
value={category}
|
||||
required
|
||||
disabled={uiState === 'loading'}
|
||||
onChange={(e) => {
|
||||
setSelectedCategory(e.target.value);
|
||||
setShowRules(e.target.value === 'violation');
|
||||
}}
|
||||
/>
|
||||
<span>
|
||||
{CATEGORIES_INFO[category].label}
|
||||
<small class="ib insignificant">
|
||||
{CATEGORIES_INFO[category].description}
|
||||
</small>
|
||||
</span>
|
||||
</label>
|
||||
{category === 'violation' && !!rules?.length && (
|
||||
<div
|
||||
class="shazam-container no-animation"
|
||||
hidden={!showRules}
|
||||
>
|
||||
<div class="shazam-container-inner">
|
||||
<div class="report-rules" ref={rulesRef}>
|
||||
{rules.map((rule, i) => (
|
||||
<label class="report-rule" key={rule.id}>
|
||||
<input
|
||||
type="checkbox"
|
||||
name={`rule_ids[${i}]`}
|
||||
value={rule.id}
|
||||
required={showRules && !hasRules}
|
||||
disabled={uiState === 'loading'}
|
||||
onChange={(e) => {
|
||||
const { checked } = e.target;
|
||||
if (checked) {
|
||||
setHasRules(true);
|
||||
} else {
|
||||
const checkedInputs =
|
||||
rulesRef.current.querySelectorAll(
|
||||
'input:checked',
|
||||
);
|
||||
if (!checkedInputs.length) {
|
||||
setHasRules(false);
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<span>{rule.text}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Fragment>
|
||||
),
|
||||
)}
|
||||
</section>
|
||||
<section class="report-comment">
|
||||
<p>
|
||||
<label for="report-comment">Additional info</label>
|
||||
</p>
|
||||
<textarea
|
||||
maxlength="1000"
|
||||
rows="1"
|
||||
name="comment"
|
||||
id="report-comment"
|
||||
disabled={uiState === 'loading'}
|
||||
/>
|
||||
</section>
|
||||
<section>
|
||||
{domain !== currentDomain && (
|
||||
<p>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
switch
|
||||
name="forward"
|
||||
disabled={uiState === 'loading'}
|
||||
/>{' '}
|
||||
<span>
|
||||
Forward to <i>{domain}</i>
|
||||
</span>
|
||||
</label>
|
||||
</p>
|
||||
)}
|
||||
</section>
|
||||
<footer>
|
||||
<button type="submit" disabled={uiState === 'loading'}>
|
||||
Send Report
|
||||
</button>{' '}
|
||||
<button
|
||||
type="submit"
|
||||
class="plain2"
|
||||
disabled={uiState === 'loading'}
|
||||
onClick={async () => {
|
||||
try {
|
||||
await masto.v1.accounts.$select(account.id).mute(); // Infinite duration
|
||||
showToast(`Muted ${username}`);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
showToast(`Unable to mute ${username}`);
|
||||
}
|
||||
// onSubmit will still run
|
||||
}}
|
||||
>
|
||||
Send Report <small class="ib">+ Mute profile</small>
|
||||
</button>{' '}
|
||||
<button
|
||||
type="submit"
|
||||
class="plain2"
|
||||
disabled={uiState === 'loading'}
|
||||
onClick={async () => {
|
||||
try {
|
||||
await masto.v1.accounts.$select(account.id).block();
|
||||
showToast(`Blocked ${username}`);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
showToast(`Unable to block ${username}`);
|
||||
}
|
||||
// onSubmit will still run
|
||||
}}
|
||||
>
|
||||
Send Report <small class="ib">+ Block profile</small>
|
||||
</button>
|
||||
<Loader hidden={uiState !== 'loading'} />
|
||||
</footer>
|
||||
</form>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ReportModal;
|
|
@ -153,6 +153,15 @@
|
|||
}
|
||||
#import-export-container section p {
|
||||
margin: 8px 0;
|
||||
|
||||
&.field-button {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
|
||||
button {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
#import-export-container section details > summary {
|
||||
cursor: pointer;
|
||||
|
@ -182,3 +191,14 @@
|
|||
font-size: 90%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
#import-export-container {
|
||||
footer {
|
||||
font-size: 90%;
|
||||
color: var(--text-insignificant-color);
|
||||
|
||||
.icon {
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ import { fetchFollowedTags } from '../utils/followed-tags';
|
|||
import pmem from '../utils/pmem';
|
||||
import showToast from '../utils/show-toast';
|
||||
import states from '../utils/states';
|
||||
import store from '../utils/store';
|
||||
|
||||
import AsyncText from './AsyncText';
|
||||
import Icon from './icon';
|
||||
|
@ -716,6 +717,7 @@ function ShortcutForm({
|
|||
}
|
||||
|
||||
function ImportExport({ shortcuts, onClose }) {
|
||||
const { masto } = api();
|
||||
const shortcutsStr = useMemo(() => {
|
||||
if (!shortcuts) return '';
|
||||
if (!shortcuts.filter(Boolean).length) return '';
|
||||
|
@ -754,6 +756,8 @@ function ImportExport({ shortcuts, onClose }) {
|
|||
}, [importShortcutStr]);
|
||||
const hasCurrentSettings = states.shortcuts.length > 0;
|
||||
|
||||
const shortcutsImportFieldRef = useRef();
|
||||
|
||||
return (
|
||||
<div id="import-export-container" class="sheet">
|
||||
{!!onClose && (
|
||||
|
@ -772,8 +776,9 @@ function ImportExport({ shortcuts, onClose }) {
|
|||
<Icon icon="arrow-down-circle" size="l" class="insignificant" />{' '}
|
||||
<span>Import</span>
|
||||
</h3>
|
||||
<p>
|
||||
<p class="field-button">
|
||||
<input
|
||||
ref={shortcutsImportFieldRef}
|
||||
type="text"
|
||||
name="import"
|
||||
placeholder="Paste shortcuts here"
|
||||
|
@ -782,6 +787,53 @@ function ImportExport({ shortcuts, onClose }) {
|
|||
setImportShortcutStr(e.target.value);
|
||||
}}
|
||||
/>
|
||||
{states.settings.shortcutSettingsCloudImportExport && (
|
||||
<button
|
||||
type="button"
|
||||
class="plain2 small"
|
||||
disabled={importUIState === 'cloud-downloading'}
|
||||
onClick={async () => {
|
||||
setImportUIState('cloud-downloading');
|
||||
const currentAccount = store.session.get('currentAccount');
|
||||
showToast(
|
||||
'Downloading saved shortcuts from instance server…',
|
||||
);
|
||||
try {
|
||||
const relationships =
|
||||
await masto.v1.accounts.relationships.fetch({
|
||||
id: [currentAccount],
|
||||
});
|
||||
const relationship = relationships[0];
|
||||
if (relationship) {
|
||||
const { note = '' } = relationship;
|
||||
if (
|
||||
/<phanpy-shortcuts-settings>(.*)<\/phanpy-shortcuts-settings>/.test(
|
||||
note,
|
||||
)
|
||||
) {
|
||||
const settings = note.match(
|
||||
/<phanpy-shortcuts-settings>(.*)<\/phanpy-shortcuts-settings>/,
|
||||
)[1];
|
||||
const { v, dt, data } = JSON.parse(settings);
|
||||
shortcutsImportFieldRef.current.value = data;
|
||||
shortcutsImportFieldRef.current.dispatchEvent(
|
||||
new Event('input'),
|
||||
);
|
||||
}
|
||||
}
|
||||
setImportUIState('default');
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
setImportUIState('error');
|
||||
showToast('Unable to download shortcuts');
|
||||
}
|
||||
}}
|
||||
title="Download shortcuts from instance server"
|
||||
>
|
||||
<Icon icon="cloud" />
|
||||
<Icon icon="arrow-down" />
|
||||
</button>
|
||||
)}
|
||||
</p>
|
||||
{!!parsedImportShortcutStr &&
|
||||
Array.isArray(parsedImportShortcutStr) && (
|
||||
|
@ -991,8 +1043,64 @@ function ImportExport({ shortcuts, onClose }) {
|
|||
<Icon icon="share" /> <span>Share</span>
|
||||
</button>
|
||||
)}{' '}
|
||||
{states.settings.shortcutSettingsCloudImportExport && (
|
||||
<button
|
||||
type="button"
|
||||
class="plain2"
|
||||
disabled={importUIState === 'cloud-uploading'}
|
||||
onClick={async () => {
|
||||
setImportUIState('cloud-uploading');
|
||||
const currentAccount = store.session.get('currentAccount');
|
||||
try {
|
||||
const relationships =
|
||||
await masto.v1.accounts.relationships.fetch({
|
||||
id: [currentAccount],
|
||||
});
|
||||
const relationship = relationships[0];
|
||||
if (relationship) {
|
||||
const { note = '' } = relationship;
|
||||
// const newNote = `${note}\n\n\n$<phanpy-shortcuts-settings>{shortcutsStr}</phanpy-shortcuts-settings>`;
|
||||
let newNote = '';
|
||||
if (
|
||||
/<phanpy-shortcuts-settings>(.*)<\/phanpy-shortcuts-settings>/.test(
|
||||
note,
|
||||
)
|
||||
) {
|
||||
const settingsJSON = JSON.stringify({
|
||||
v: '1', // version
|
||||
dt: Date.now(), // datetime stamp
|
||||
data: shortcutsStr, // shortcuts settings string
|
||||
});
|
||||
newNote = note.replace(
|
||||
/<phanpy-shortcuts-settings>(.*)<\/phanpy-shortcuts-settings>/,
|
||||
`<phanpy-shortcuts-settings>${settingsJSON}</phanpy-shortcuts-settings>`,
|
||||
);
|
||||
} else {
|
||||
newNote = `${note}\n\n\n<phanpy-shortcuts-settings>${settingsJSON}</phanpy-shortcuts-settings>`;
|
||||
}
|
||||
showToast('Saving shortcuts to instance server…');
|
||||
await masto.v1.accounts
|
||||
.$select(currentAccount)
|
||||
.note.create({
|
||||
comment: newNote,
|
||||
});
|
||||
setImportUIState('default');
|
||||
showToast('Shortcuts saved');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
setImportUIState('error');
|
||||
showToast('Unable to save shortcuts');
|
||||
}
|
||||
}}
|
||||
title="Sync to instance server"
|
||||
>
|
||||
<Icon icon="cloud" />
|
||||
<Icon icon="arrow-up" />
|
||||
</button>
|
||||
)}{' '}
|
||||
{shortcutsStr.length > 0 && (
|
||||
<small class="insignificant">
|
||||
<small class="insignificant ib">
|
||||
{shortcutsStr.length} characters
|
||||
</small>
|
||||
)}
|
||||
|
@ -1008,6 +1116,14 @@ function ImportExport({ shortcuts, onClose }) {
|
|||
</details>
|
||||
)}
|
||||
</section>
|
||||
{states.settings.shortcutSettingsCloudImportExport && (
|
||||
<footer>
|
||||
<p>
|
||||
<Icon icon="cloud" /> Import/export settings from/to instance
|
||||
server (Very experimental)
|
||||
</p>
|
||||
</footer>
|
||||
)}
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -559,12 +559,11 @@ function Status({
|
|||
if (reblogged) {
|
||||
const newStatus = await masto.v1.statuses.$select(id).unreblog();
|
||||
saveStatus(newStatus, instance);
|
||||
return true;
|
||||
} else {
|
||||
const newStatus = await masto.v1.statuses.$select(id).reblog();
|
||||
saveStatus(newStatus, instance);
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
// Revert optimistism
|
||||
|
@ -575,7 +574,8 @@ function Status({
|
|||
|
||||
const favouriteStatus = async () => {
|
||||
if (!sameInstance || !authenticated) {
|
||||
return alert(unauthInteractionErrorMessage);
|
||||
alert(unauthInteractionErrorMessage);
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
// Optimistic
|
||||
|
@ -591,16 +591,31 @@ function Status({
|
|||
const newStatus = await masto.v1.statuses.$select(id).favourite();
|
||||
saveStatus(newStatus, instance);
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
// Revert optimistism
|
||||
states.statuses[sKey] = status;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
const favouriteStatusNotify = async () => {
|
||||
try {
|
||||
const done = await favouriteStatus();
|
||||
if (!isSizeLarge && done) {
|
||||
showToast(
|
||||
favourited
|
||||
? `Unliked @${username || acct}'s post`
|
||||
: `Liked @${username || acct}'s post`,
|
||||
);
|
||||
}
|
||||
} catch (e) {}
|
||||
};
|
||||
|
||||
const bookmarkStatus = async () => {
|
||||
if (!sameInstance || !authenticated) {
|
||||
return alert(unauthInteractionErrorMessage);
|
||||
alert(unauthInteractionErrorMessage);
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
// Optimistic
|
||||
|
@ -615,12 +630,26 @@ function Status({
|
|||
const newStatus = await masto.v1.statuses.$select(id).bookmark();
|
||||
saveStatus(newStatus, instance);
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
// Revert optimistism
|
||||
states.statuses[sKey] = status;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
const bookmarkStatusNotify = async () => {
|
||||
try {
|
||||
const done = await bookmarkStatus();
|
||||
if (!isSizeLarge && done) {
|
||||
showToast(
|
||||
bookmarked
|
||||
? `Unbookmarked @${username || acct}'s post`
|
||||
: `Bookmarked @${username || acct}'s post`,
|
||||
);
|
||||
}
|
||||
} catch (e) {}
|
||||
};
|
||||
|
||||
const differentLanguage =
|
||||
!!language &&
|
||||
|
@ -752,18 +781,7 @@ function Status({
|
|||
</span>
|
||||
</MenuConfirm>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
try {
|
||||
favouriteStatus();
|
||||
if (!isSizeLarge) {
|
||||
showToast(
|
||||
favourited
|
||||
? `Unliked @${username || acct}'s post`
|
||||
: `Liked @${username || acct}'s post`,
|
||||
);
|
||||
}
|
||||
} catch (e) {}
|
||||
}}
|
||||
onClick={favouriteStatusNotify}
|
||||
className={`menu-favourite ${favourited ? 'checked' : ''}`}
|
||||
>
|
||||
<Icon icon="heart" />
|
||||
|
@ -776,18 +794,7 @@ function Status({
|
|||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
try {
|
||||
bookmarkStatus();
|
||||
if (!isSizeLarge) {
|
||||
showToast(
|
||||
bookmarked
|
||||
? `Unbookmarked @${username || acct}'s post`
|
||||
: `Bookmarked @${username || acct}'s post`,
|
||||
);
|
||||
}
|
||||
} catch (e) {}
|
||||
}}
|
||||
onClick={bookmarkStatusNotify}
|
||||
className={`menu-bookmark ${bookmarked ? 'checked' : ''}`}
|
||||
>
|
||||
<Icon icon="bookmark" />
|
||||
|
@ -1040,6 +1047,23 @@ function Status({
|
|||
)}
|
||||
</div>
|
||||
)}
|
||||
{!isSelf && isSizeLarge && (
|
||||
<>
|
||||
<MenuDivider />
|
||||
<MenuItem
|
||||
className="danger"
|
||||
onClick={() => {
|
||||
states.showReportModal = {
|
||||
account: status.account,
|
||||
post: status,
|
||||
};
|
||||
}}
|
||||
>
|
||||
<Icon icon="flag" />
|
||||
<span>Report post…</span>
|
||||
</MenuItem>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
|
@ -1085,42 +1109,12 @@ function Status({
|
|||
const rRef = useHotkeys('r, shift+r', replyStatus, {
|
||||
enabled: hotkeysEnabled,
|
||||
});
|
||||
const fRef = useHotkeys(
|
||||
'f, l',
|
||||
() => {
|
||||
try {
|
||||
favouriteStatus();
|
||||
if (!isSizeLarge) {
|
||||
showToast(
|
||||
favourited
|
||||
? `Unliked @${username || acct}'s post`
|
||||
: `Liked @${username || acct}'s post`,
|
||||
);
|
||||
}
|
||||
} catch (e) {}
|
||||
},
|
||||
{
|
||||
enabled: hotkeysEnabled,
|
||||
},
|
||||
);
|
||||
const dRef = useHotkeys(
|
||||
'd',
|
||||
() => {
|
||||
try {
|
||||
bookmarkStatus();
|
||||
if (!isSizeLarge) {
|
||||
showToast(
|
||||
bookmarked
|
||||
? `Unbookmarked @${username || acct}'s post`
|
||||
: `Bookmarked @${username || acct}'s post`,
|
||||
);
|
||||
}
|
||||
} catch (e) {}
|
||||
},
|
||||
{
|
||||
enabled: hotkeysEnabled,
|
||||
},
|
||||
);
|
||||
const fRef = useHotkeys('f, l', favouriteStatusNotify, {
|
||||
enabled: hotkeysEnabled,
|
||||
});
|
||||
const dRef = useHotkeys('d', bookmarkStatusNotify, {
|
||||
enabled: hotkeysEnabled,
|
||||
});
|
||||
const bRef = useHotkeys(
|
||||
'shift+b',
|
||||
() => {
|
||||
|
@ -1420,16 +1414,7 @@ function Status({
|
|||
icon="heart"
|
||||
iconSize="m"
|
||||
count={favouritesCount}
|
||||
onClick={() => {
|
||||
try {
|
||||
favouriteStatus();
|
||||
showToast(
|
||||
favourited
|
||||
? `Unliked @${username || acct}'s post`
|
||||
: `Liked @${username || acct}'s post`,
|
||||
);
|
||||
} catch (e) {}
|
||||
}}
|
||||
onClick={favouriteStatusNotify}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
|
|
832
src/pages/catchup.css
Normal file
832
src/pages/catchup.css
Normal file
|
@ -0,0 +1,832 @@
|
|||
#catchup-page {
|
||||
transform: none;
|
||||
padding-bottom: 0 !important;
|
||||
|
||||
.deck {
|
||||
background-color: var(--bg-faded-color);
|
||||
}
|
||||
|
||||
/* Hide the shortcuts nav + adjustments */
|
||||
~ :is(#shortcuts, #compose-button) {
|
||||
display: none;
|
||||
}
|
||||
.timeline-deck {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
header {
|
||||
/* --margin-top: 8px !important; */
|
||||
position: static;
|
||||
}
|
||||
|
||||
h1 sup {
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
font-weight: 500;
|
||||
color: var(--text-insignificant-color);
|
||||
}
|
||||
|
||||
.catchup-start {
|
||||
padding: 16px;
|
||||
padding-top: 15vh;
|
||||
text-align: center;
|
||||
max-width: 40em;
|
||||
margin-inline: auto;
|
||||
|
||||
.catchup-info {
|
||||
animation: appear 0.3s ease-out;
|
||||
display: flex;
|
||||
gap: 0.25em;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.catchup-prev {
|
||||
margin: 2em auto;
|
||||
padding: 1em;
|
||||
width: fit-content;
|
||||
color: var(--text-insignificant-color);
|
||||
border-top: 1px solid var(--bg-color);
|
||||
|
||||
ul,
|
||||
ul li {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
ul li {
|
||||
display: flex;
|
||||
margin-bottom: 8px;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
text-align: left;
|
||||
justify-content: space-between;
|
||||
|
||||
a {
|
||||
display: flex;
|
||||
gap: 0.25em;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.catchup-form {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding: 16px 16px;
|
||||
background-color: var(--link-bg-color);
|
||||
border-radius: 32px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
* {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
input[type='range'] {
|
||||
accent-color: var(--link-color);
|
||||
direction: rtl;
|
||||
}
|
||||
}
|
||||
|
||||
.catchup-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 16px;
|
||||
gap: 8px;
|
||||
|
||||
aside {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 90%;
|
||||
|
||||
button[hidden] {
|
||||
display: inline;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.catchup-posts-viz-bar {
|
||||
margin: 0 16px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid var(--bg-color);
|
||||
display: flex;
|
||||
gap: 1px;
|
||||
pointer-events: none;
|
||||
justify-content: stretch;
|
||||
height: 3px;
|
||||
|
||||
&:has(.post-dot:nth-child(320)) {
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.post-dot {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
border-radius: 3px;
|
||||
opacity: 0.5;
|
||||
background-color: var(--link-color);
|
||||
transition: 0.25s ease-in-out;
|
||||
transition-property: opacity, transform;
|
||||
|
||||
&.post-dot-highlight {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&:has(.post-dot:not(.post-dot-highlight)) .post-dot-highlight {
|
||||
/* transform: scaleY(2); */
|
||||
transform: scale3d(1, 2, 1);
|
||||
}
|
||||
}
|
||||
|
||||
.catchup-filters {
|
||||
padding: 8px 16px;
|
||||
display: flex;
|
||||
/* flex-wrap: wrap; */
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
max-width: 100%;
|
||||
mask-image: linear-gradient(
|
||||
to right,
|
||||
transparent,
|
||||
black 16px calc(100% - 16px),
|
||||
transparent
|
||||
);
|
||||
padding-inline-end: 25%;
|
||||
|
||||
.filter-label {
|
||||
text-transform: uppercase;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: var(--text-insignificant-color);
|
||||
}
|
||||
|
||||
label {
|
||||
font-size: 80%;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
color: var(--text-insignificant-color);
|
||||
position: relative;
|
||||
|
||||
select {
|
||||
/* appearance: none;
|
||||
background-color: var(--bg-faded-color);
|
||||
color: var(--link-color);
|
||||
border: 0;
|
||||
border-radius: 12px; */
|
||||
padding: 4px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
input[type] {
|
||||
left: 0;
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&.filter-cat {
|
||||
padding: 8px 12px;
|
||||
background-color: var(--bg-blur-color);
|
||||
border-radius: 24px;
|
||||
display: inline-block;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
|
||||
&:is(:hover, :focus) {
|
||||
background-color: var(--link-bg-color);
|
||||
}
|
||||
|
||||
&:has(:checked) {
|
||||
color: var(--link-color);
|
||||
background-color: var(--link-bg-color);
|
||||
box-shadow: inset 0 0 0 2px var(--link-color);
|
||||
}
|
||||
|
||||
.count {
|
||||
font-size: 70%;
|
||||
margin-left: 4px;
|
||||
background-color: var(--bg-color);
|
||||
padding: 4px 6px;
|
||||
border-radius: 12px;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
&.filter-author {
|
||||
flex-direction: column;
|
||||
padding: 0;
|
||||
background-color: transparent;
|
||||
position: relative;
|
||||
width: 50px;
|
||||
/* transition: filter 0.15s ease; */
|
||||
|
||||
.avatar {
|
||||
margin-bottom: 2px;
|
||||
/* transition: box-shadow 0.15s ease; */
|
||||
}
|
||||
|
||||
&:is(:hover, :focus) {
|
||||
filter: none;
|
||||
|
||||
.avatar {
|
||||
box-shadow: 0 0 0 0.5px var(--bg-color),
|
||||
0 0 0 3px var(--link-faded-color) !important;
|
||||
|
||||
img {
|
||||
filter: none;
|
||||
}
|
||||
}
|
||||
|
||||
.count {
|
||||
color: var(--text-color);
|
||||
}
|
||||
}
|
||||
|
||||
.avatar {
|
||||
&.has-alpha {
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
img {
|
||||
transition: filter 0.15s ease;
|
||||
}
|
||||
}
|
||||
|
||||
&:has(:checked) {
|
||||
box-shadow: none;
|
||||
filter: none;
|
||||
|
||||
.avatar {
|
||||
box-shadow: 0 0 0 1px var(--bg-color), 0 0 0 3px var(--link-color);
|
||||
}
|
||||
|
||||
.username {
|
||||
color: var(--link-color);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.count {
|
||||
color: var(--link-color);
|
||||
border-color: var(--link-color);
|
||||
box-shadow: 0 0 0 1px var(--link-color);
|
||||
}
|
||||
}
|
||||
|
||||
.count {
|
||||
position: absolute;
|
||||
right: -4px;
|
||||
top: -4px;
|
||||
font-size: 10px;
|
||||
background-color: var(--bg-color);
|
||||
border-radius: 12px;
|
||||
display: inline-block;
|
||||
border: 1px solid var(--link-faded-color);
|
||||
padding: 0 2px;
|
||||
min-width: 16px;
|
||||
min-height: 16px;
|
||||
text-align: center;
|
||||
line-height: 14px;
|
||||
}
|
||||
|
||||
.username {
|
||||
display: block;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
mask-image: linear-gradient(
|
||||
to right,
|
||||
black calc(100% - 0.5em),
|
||||
transparent 100%
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:has(.filter-author :checked)
|
||||
.filter-author:not(:has(:checked)):not(:is(:hover, :focus)) {
|
||||
.avatar img {
|
||||
filter: grayscale(1) contrast(2) opacity(0.5);
|
||||
}
|
||||
}
|
||||
|
||||
.radio-field-group {
|
||||
display: flex;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
gap: 1px;
|
||||
|
||||
label {
|
||||
padding: 4px 8px;
|
||||
line-height: 2em;
|
||||
min-width: 32px;
|
||||
text-align: center;
|
||||
background-color: var(--bg-blur-color);
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
|
||||
&:is(:hover, :focus) {
|
||||
background-color: var(--link-bg-color);
|
||||
}
|
||||
|
||||
&:has(:checked) {
|
||||
font-weight: 500;
|
||||
color: var(--text-color);
|
||||
background-color: var(--link-bg-color);
|
||||
box-shadow: inset 0 -2px 0 var(--link-color);
|
||||
}
|
||||
|
||||
&:has(:disabled) {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.catchup-list {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
/* background-color: var(--bg-color); */
|
||||
border-top: var(--hairline-width) solid var(--bg-faded-color);
|
||||
--corner-radius: 8px;
|
||||
|
||||
@media (min-width: 40em) {
|
||||
border-radius: var(--corner-radius);
|
||||
/* border: var(--hairline-width) solid var(--outline-color); */
|
||||
|
||||
> li {
|
||||
&:first-child > a {
|
||||
border-top-left-radius: var(--corner-radius);
|
||||
border-top-right-radius: var(--corner-radius);
|
||||
}
|
||||
|
||||
&:last-child > a {
|
||||
border-bottom-left-radius: var(--corner-radius);
|
||||
border-bottom-right-radius: var(--corner-radius);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> li {
|
||||
margin: 0 0 1px;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
/* border-bottom: var(--hairline-width) solid var(--outline-color); */
|
||||
|
||||
&.separator {
|
||||
height: 16px;
|
||||
pointer-events: none;
|
||||
|
||||
@media (min-width: 40em) {
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 40em) {
|
||||
&.separator + li a {
|
||||
border-top-left-radius: var(--corner-radius);
|
||||
border-top-right-radius: var(--corner-radius);
|
||||
}
|
||||
|
||||
&:has(+ .separator) a {
|
||||
border-bottom-left-radius: var(--corner-radius);
|
||||
border-bottom-right-radius: var(--corner-radius);
|
||||
}
|
||||
}
|
||||
|
||||
> a {
|
||||
background-color: var(--bg-color);
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
display: block;
|
||||
/* transition: background-color 0.3s ease; */
|
||||
|
||||
&:is(:hover, :focus) {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
background-color: var(--bg-faded-color);
|
||||
box-shadow: 0 8px 16px -8px var(--drop-shadow-color),
|
||||
inset 0 1px var(--bg-color);
|
||||
outline: 1px solid var(--outline-color);
|
||||
text-shadow: 0 1px var(--bg-color);
|
||||
}
|
||||
|
||||
&:active {
|
||||
filter: brightness(0.95);
|
||||
box-shadow: none;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
&:visited {
|
||||
color: var(--outline-color);
|
||||
|
||||
*,
|
||||
.post-peek-html * a[href] {
|
||||
color: var(--outline-color) !important;
|
||||
|
||||
* {
|
||||
color: var(--outline-color) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.post-line {
|
||||
border-radius: inherit;
|
||||
animation: appear-smooth 0.3s ease-in-out both;
|
||||
--pad: 16px;
|
||||
min-height: 44px;
|
||||
padding: var(--pad);
|
||||
column-gap: calc(0.5 * var(--pad));
|
||||
row-gap: 4px;
|
||||
width: 100%;
|
||||
/* display: flex;
|
||||
flex-direction: column; */
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
grid-template-rows: auto auto;
|
||||
grid-template-areas:
|
||||
'author meta'
|
||||
'content content';
|
||||
/* align-items: center; */
|
||||
background-image: linear-gradient(
|
||||
160deg,
|
||||
var(--post-bg-color),
|
||||
transparent min(80px, 50%)
|
||||
);
|
||||
/* background-image: linear-gradient(
|
||||
90deg,
|
||||
var(--post-bg-color),
|
||||
var(--post-bg-color) 8px,
|
||||
transparent 8px
|
||||
); */
|
||||
|
||||
@media (min-width: 40em) {
|
||||
/* flex-direction: row;
|
||||
align-items: center; */
|
||||
grid-template-columns: auto 1fr auto;
|
||||
grid-template-rows: 1fr;
|
||||
grid-template-areas: 'author content meta';
|
||||
}
|
||||
|
||||
&.reblog {
|
||||
--post-bg-color: var(--reblog-faded-color);
|
||||
}
|
||||
&.group {
|
||||
--post-bg-color: var(--group-faded-color);
|
||||
}
|
||||
&.reply-to {
|
||||
--post-bg-color: var(--reply-to-faded-color);
|
||||
}
|
||||
&.followed-tags {
|
||||
--post-bg-color: var(--hashtag-faded-color);
|
||||
}
|
||||
&.filtered {
|
||||
filter: grayscale(1);
|
||||
background-image: none;
|
||||
|
||||
.post-author {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
&.visibility-direct {
|
||||
--yellow-stripes: repeating-linear-gradient(
|
||||
-45deg,
|
||||
var(--reply-to-faded-color),
|
||||
var(--reply-to-faded-color) 10px,
|
||||
var(--reply-to-faded-color) 10px,
|
||||
transparent 10px,
|
||||
transparent 20px
|
||||
);
|
||||
background-image: var(--yellow-stripes);
|
||||
}
|
||||
|
||||
.post-reblog-avatar {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
|
||||
.icon {
|
||||
color: var(--reblog-color);
|
||||
}
|
||||
}
|
||||
|
||||
.post-author {
|
||||
grid-area: author;
|
||||
flex-shrink: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
mask-image: linear-gradient(
|
||||
to right,
|
||||
black calc(100% - 1em),
|
||||
transparent 100%
|
||||
);
|
||||
|
||||
@media (min-width: 40em) {
|
||||
--width: 25vw;
|
||||
width: var(--width);
|
||||
min-width: 9em;
|
||||
max-width: 13em;
|
||||
}
|
||||
|
||||
b {
|
||||
font-weight: normal;
|
||||
/* font-weight: 500; */
|
||||
opacity: 0.7;
|
||||
}
|
||||
i {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> li:nth-child(10) ~ li .post-line {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.post-peek {
|
||||
grid-area: content;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
column-gap: 8px;
|
||||
row-gap: 4px;
|
||||
align-self: stretch;
|
||||
/* align-items: center; */
|
||||
/* margin-left: 24px; */
|
||||
flex-direction: row-reverse;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
|
||||
/* CLOAK - uncomment when taking screenshots */
|
||||
/* text-decoration-thickness: 1.1em;
|
||||
text-decoration-line: line-through;
|
||||
text-rendering: optimizeSpeed;
|
||||
filter: opacity(0.5);
|
||||
pointer-events: none;
|
||||
img {
|
||||
filter: contrast(0) !important;
|
||||
background-color: #000 !important;
|
||||
} */
|
||||
|
||||
.post-peek-content {
|
||||
flex-shrink: 1;
|
||||
flex-grow: 1;
|
||||
flex-basis: 20em;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
line-height: 1.3;
|
||||
opacity: 0.9;
|
||||
/* font-size: 0.9em; */
|
||||
text-wrap: balance;
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.post-peek-html {
|
||||
pointer-events: none;
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: inline;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
pre,
|
||||
code {
|
||||
font-size: 0.9em;
|
||||
color: var(--green-color);
|
||||
}
|
||||
|
||||
strong,
|
||||
b {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
br {
|
||||
content: ' ';
|
||||
}
|
||||
|
||||
/* all block level elements */
|
||||
p,
|
||||
div,
|
||||
blockquote,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
li,
|
||||
pre,
|
||||
br {
|
||||
&:after {
|
||||
content: ' ';
|
||||
}
|
||||
}
|
||||
|
||||
br:after {
|
||||
font-size: 0.75em;
|
||||
content: ' ↵ ';
|
||||
opacity: 0.35;
|
||||
}
|
||||
|
||||
.ellipsis:after {
|
||||
content: '...';
|
||||
}
|
||||
|
||||
.invisible {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Links are not clickable, so remove the underlines */
|
||||
a {
|
||||
text-decoration: none;
|
||||
text-decoration-color: transparent;
|
||||
color: var(--link-text-color);
|
||||
}
|
||||
}
|
||||
|
||||
.post-peek-spoiler {
|
||||
line-height: 1.5;
|
||||
border-radius: 1em;
|
||||
padding-inline: 0.5em;
|
||||
border: 1px dashed var(--button-bg-color);
|
||||
|
||||
.icon {
|
||||
vertical-align: middle;
|
||||
color: var(--button-bg-color);
|
||||
}
|
||||
}
|
||||
|
||||
.post-peek-filtered {
|
||||
border-radius: 1em;
|
||||
padding-inline: 0.5em;
|
||||
border: 1px dashed var(--outline-hover-color);
|
||||
|
||||
.icon {
|
||||
vertical-align: middle;
|
||||
color: var(--outline-hover-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.post-peek-post-content {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-self: center;
|
||||
transition: transform 0.05s ease-in-out;
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:has(.post-peek-media):hover {
|
||||
transform: scale(1.5);
|
||||
}
|
||||
|
||||
img {
|
||||
border-radius: 2px;
|
||||
outline: var(--hairline-width) solid var(--outline-color);
|
||||
vertical-align: middle;
|
||||
object-fit: cover;
|
||||
box-shadow: 0 0 0 1px var(--outline-color);
|
||||
object-fit: cover;
|
||||
transition: transform 0.05s ease-in-out;
|
||||
background-color: var(--bg-color);
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.5);
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
animation: position-object 5s ease-in-out 5;
|
||||
|
||||
/* @media (min-width: 40em) and (min-height: 600px) {
|
||||
transform: scale(3);
|
||||
} */
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 40em) {
|
||||
&:has(.post-peek-media),
|
||||
.post-peek-media:first-child img {
|
||||
transform-origin: left center;
|
||||
}
|
||||
}
|
||||
|
||||
.post-peek-faux-media {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 4px;
|
||||
background-color: var(--bg-faded-color);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 0 0 1px var(--outline-color);
|
||||
}
|
||||
|
||||
.post-peek-card img {
|
||||
outline: 3px double var(--link-faded-color);
|
||||
}
|
||||
}
|
||||
|
||||
.post-peek-tag {
|
||||
display: inline-block;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
color: var(--text-insignificant-color);
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
border: 1px solid var(--outline-color);
|
||||
padding: 2px !important;
|
||||
align-self: center;
|
||||
background-color: var(--bg-faded-color);
|
||||
line-height: 1;
|
||||
|
||||
&.post-peek-poll {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
flex-direction: column;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
&.post-peek-thread {
|
||||
font-size: 10px;
|
||||
/* padding: 2px 4px; */
|
||||
font-weight: 700;
|
||||
background-color: var(--bg-color);
|
||||
color: var(--reply-to-text-color) !important;
|
||||
border-color: var(--reply-to-color);
|
||||
background-image: repeating-linear-gradient(
|
||||
-70deg,
|
||||
transparent,
|
||||
transparent 3px,
|
||||
var(--reply-to-faded-color) 3px,
|
||||
var(--reply-to-faded-color) 4px
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
> li > a:is(:hover, :focus) .post-peek-content {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.post-meta {
|
||||
grid-area: meta;
|
||||
font-size: 90%;
|
||||
color: var(--text-insignificant-color);
|
||||
white-space: nowrap;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-self: flex-start;
|
||||
column-gap: 8px;
|
||||
}
|
||||
|
||||
.post-stats {
|
||||
opacity: 0;
|
||||
display: inline-flex;
|
||||
gap: 2px;
|
||||
align-items: center;
|
||||
transform: translateX(4px);
|
||||
/* transition: all 0.25s ease-out; */
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.post-line:hover .post-stats {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
+ footer {
|
||||
min-height: 15vh;
|
||||
color: var(--text-insignificant-color);
|
||||
padding-block: 15vh;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
1371
src/pages/catchup.jsx
Normal file
1371
src/pages/catchup.jsx
Normal file
File diff suppressed because it is too large
Load diff
|
@ -464,6 +464,39 @@ function Settings({ onClose }) {
|
|||
</div>
|
||||
</li>
|
||||
)}
|
||||
{authenticated && (
|
||||
<li>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={
|
||||
snapStates.settings.shortcutSettingsCloudImportExport
|
||||
}
|
||||
onChange={(e) => {
|
||||
states.settings.shortcutSettingsCloudImportExport =
|
||||
e.target.checked;
|
||||
}}
|
||||
/>{' '}
|
||||
"Cloud" import/export for shortcuts settings{' '}
|
||||
<Icon icon="cloud" class="more-insignificant" />
|
||||
</label>
|
||||
<div class="sub-section insignificant">
|
||||
<small>
|
||||
⚠️⚠️⚠️ Very experimental.
|
||||
<br />
|
||||
Stored in your own profile’s notes. Profile (private) notes
|
||||
are mainly used for other profiles, and hidden for own
|
||||
profile.
|
||||
</small>
|
||||
</div>
|
||||
<div class="sub-section insignificant">
|
||||
<small>
|
||||
Note: This feature uses currently-logged-in instance server
|
||||
API.
|
||||
</small>
|
||||
</div>
|
||||
</li>
|
||||
)}
|
||||
<li>
|
||||
<label>
|
||||
<input
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import './trending.css';
|
||||
import '../components/links-bar.css';
|
||||
|
||||
import { MenuItem } from '@szhsin/react-menu';
|
||||
import { getBlurHashAverageColor } from 'fast-blurhash';
|
||||
|
|
|
@ -9,20 +9,20 @@ import {
|
|||
set,
|
||||
} from 'idb-keyval';
|
||||
|
||||
const draftsStore = createStore('drafts-db', 'drafts-store');
|
||||
|
||||
// Add additonal `draftsStore` parameter to all methods
|
||||
|
||||
const drafts = {
|
||||
set: (key, val) => set(key, val, draftsStore),
|
||||
get: (key) => get(key, draftsStore),
|
||||
getMany: (keys) => getMany(keys, draftsStore),
|
||||
del: (key) => del(key, draftsStore),
|
||||
delMany: (keys) => delMany(keys, draftsStore),
|
||||
clear: () => clear(draftsStore),
|
||||
keys: () => keys(draftsStore),
|
||||
};
|
||||
function initDB(dbName, storeName) {
|
||||
const store = createStore(dbName, storeName);
|
||||
return {
|
||||
set: (key, val) => set(key, val, store),
|
||||
get: (key) => get(key, store),
|
||||
getMany: (keys) => getMany(keys, store),
|
||||
del: (key) => del(key, store),
|
||||
delMany: (keys) => delMany(keys, store),
|
||||
clear: () => clear(store),
|
||||
keys: () => keys(store),
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
drafts,
|
||||
drafts: initDB('drafts-db', 'drafts-store'),
|
||||
catchup: initDB('catchup-db', 'catchup-store'),
|
||||
};
|
||||
|
|
|
@ -52,6 +52,7 @@ const states = proxy({
|
|||
showGenericAccounts: false,
|
||||
showMediaAlt: false,
|
||||
showEmbedModal: false,
|
||||
showReportModal: false,
|
||||
// Shortcuts
|
||||
shortcuts: [],
|
||||
// Settings
|
||||
|
@ -64,6 +65,7 @@ const states = proxy({
|
|||
contentTranslationTargetLanguage: null,
|
||||
contentTranslationHideLanguages: [],
|
||||
contentTranslationAutoInline: false,
|
||||
shortcutSettingsCloudImportExport: false,
|
||||
mediaAltGenerator: false,
|
||||
cloakMode: false,
|
||||
},
|
||||
|
@ -93,6 +95,8 @@ export function initStates() {
|
|||
store.account.get('settings-contentTranslationHideLanguages') || [];
|
||||
states.settings.contentTranslationAutoInline =
|
||||
store.account.get('settings-contentTranslationAutoInline') ?? false;
|
||||
states.settings.shortcutSettingsCloudImportExport =
|
||||
store.account.get('settings-shortcutSettingsCloudImportExport') ?? false;
|
||||
states.settings.mediaAltGenerator =
|
||||
store.account.get('settings-mediaAltGenerator') ?? false;
|
||||
states.settings.cloakMode = store.account.get('settings-cloakMode') ?? false;
|
||||
|
@ -120,6 +124,9 @@ subscribe(states, (changes) => {
|
|||
if (path.join('.') === 'settings.contentTranslationAutoInline') {
|
||||
store.account.set('settings-contentTranslationAutoInline', !!value);
|
||||
}
|
||||
if (path.join('.') === 'settings.shortcutSettingsCloudImportExport') {
|
||||
store.account.set('settings-shortcutSettingsCloudImportExport', !!value);
|
||||
}
|
||||
if (path.join('.') === 'settings.contentTranslationTargetLanguage') {
|
||||
console.log('SET', value);
|
||||
store.account.set('settings-contentTranslationTargetLanguage', value);
|
||||
|
|
Loading…
Reference in a new issue