diff --git a/src/components/common/CollapsibleSection.tsx b/src/components/common/CollapsibleSection.tsx
new file mode 100644
index 00000000..1f31e21b
--- /dev/null
+++ b/src/components/common/CollapsibleSection.tsx
@@ -0,0 +1,52 @@
+import Details from "../ui/Details";
+import { State, store } from "../../redux";
+import { Action } from "../../redux/reducers";
+import { Children } from "../../types/Preact";
+import { ChevronDown } from "@styled-icons/boxicons-regular";
+
+interface Props {
+ id: string;
+ defaultValue: boolean;
+
+ sticky?: boolean;
+ large?: boolean;
+
+ summary: Children;
+ children: Children;
+}
+
+export default function CollapsibleSection({ id, defaultValue, summary, children, ...detailsProps }: Props) {
+ const state: State = store.getState();
+
+ function setState(state: boolean) {
+ if (state === defaultValue) {
+ store.dispatch({
+ type: 'SECTION_TOGGLE_UNSET',
+ id
+ } as Action);
+ } else {
+ store.dispatch({
+ type: 'SECTION_TOGGLE_SET',
+ id,
+ state
+ } as Action);
+ }
+ }
+
+ return (
+ setState(e.currentTarget.open)}
+ {...detailsProps}>
+
+
+ { summary }
+ {/**/}
+ {/**/}
+ {/*
*/}
+ {/**/}
+
+ { children }
+
+ )
+}
diff --git a/src/components/navigation/left/HomeSidebar.tsx b/src/components/navigation/left/HomeSidebar.tsx
index edde0aaa..16e586bb 100644
--- a/src/components/navigation/left/HomeSidebar.tsx
+++ b/src/components/navigation/left/HomeSidebar.tsx
@@ -1,4 +1,4 @@
-import { Localizer, Text } from "preact-i18n";
+import { Text } from "preact-i18n";
import { useContext, useEffect } from "preact/hooks";
import { Home, UserDetail, Wrench, Notepad } from "@styled-icons/boxicons-solid";
@@ -105,16 +105,9 @@ function HomeSidebar(props: Props) {
)}
-
-
- ) as any
- }
- action={() => openScreen({ id: "special_input", type: "create_group" })}
- />
-
+ }
+ action={() => openScreen({ id: "special_input", type: "create_group" })} />
{channelsArr.length === 0 && }
{channelsArr.map(x => {
let user;
diff --git a/src/components/navigation/left/ServerSidebar.tsx b/src/components/navigation/left/ServerSidebar.tsx
index 49e57959..3dd7aff9 100644
--- a/src/components/navigation/left/ServerSidebar.tsx
+++ b/src/components/navigation/left/ServerSidebar.tsx
@@ -14,6 +14,7 @@ import ServerHeader from "../../common/ServerHeader";
import { useEffect } from "preact/hooks";
import Category from "../../ui/Category";
import ConditionalLink from "../../../lib/ConditionalLink";
+import CollapsibleSection from "../../common/CollapsibleSection";
interface Props {
unreads: Unreads;
@@ -69,6 +70,7 @@ function ServerSidebar(props: Props & WithDispatcher) {
let uncategorised = new Set(server.channels);
let elements = [];
+
function addChannel(id: string) {
const entry = channels.find(x => x._id === id);
if (!entry) return;
@@ -76,9 +78,8 @@ function ServerSidebar(props: Props & WithDispatcher) {
const active = channel?._id === entry._id;
return (
-
+
);
-
+ let channels = [];
for (let id of category.channels) {
uncategorised.delete(id);
- elements.push(addChannel(id));
+ channels.push(addChannel(id));
}
+
+ elements.push(
+ }>
+ { channels }
+
+ );
}
}
- for (let id of uncategorised) {
+ for (let id of Array.from(uncategorised).reverse()) {
elements.unshift(addChannel(id));
}
diff --git a/src/components/ui/Category.tsx b/src/components/ui/Category.tsx
index ccdd4e91..3f76d284 100644
--- a/src/components/ui/Category.tsx
+++ b/src/components/ui/Category.tsx
@@ -31,7 +31,7 @@ const CategoryBase = styled.div>`
` }
`;
-type Props = Omit, 'children' | 'as'> & {
+type Props = Omit, 'children' | 'as' | 'action'> & {
text: Children;
action?: () => void;
variant?: 'default' | 'uniform';
diff --git a/src/components/ui/Details.tsx b/src/components/ui/Details.tsx
index 8bf47059..0a032022 100644
--- a/src/components/ui/Details.tsx
+++ b/src/components/ui/Details.tsx
@@ -1,16 +1,29 @@
-import styled from "styled-components";
+import styled, { css } from "styled-components";
-export default styled.details`
+export default styled.details<{ sticky?: boolean, large?: boolean }>`
summary {
+ ${ props => props.sticky && css`
+ top: -1px;
+ z-index: 10;
+ position: sticky;
+ ` }
+
+ ${ props => props.large && css`
+ padding: 5px 0;
+ ` }
+
outline: none;
+ display: flex;
+ cursor: pointer;
list-style: none;
+ align-items: center;
transition: .2s opacity;
&::marker, &::-webkit-details-marker {
display: none;
}
- svg {
+ > svg {
flex-shrink: 0;
transition: .2s ease transform;
}
diff --git a/src/components/ui/Overline.tsx b/src/components/ui/Overline.tsx
index 5a9f9d04..17159077 100644
--- a/src/components/ui/Overline.tsx
+++ b/src/components/ui/Overline.tsx
@@ -5,6 +5,7 @@ import { Text } from 'preact-i18n';
type Props = Omit, 'children' | 'as'> & {
error?: string;
block?: boolean;
+ spaced?: boolean;
children?: Children;
type?: "default" | "subtle" | "error";
}
@@ -12,7 +13,10 @@ type Props = Omit, 'children' | 'as'> & {
const OverlineBase = styled.div>`
display: inline;
margin: 0.4em 0;
- margin-top: 0.8em;
+
+ ${ props => props.spaced && css`
+ margin-top: 0.8em;
+ ` }
font-size: 14px;
font-weight: 600;
diff --git a/src/context/Locale.tsx b/src/context/Locale.tsx
index 4afc9f50..873d57a6 100644
--- a/src/context/Locale.tsx
+++ b/src/context/Locale.tsx
@@ -150,12 +150,6 @@ function Locale({ children, locale }: Props) {
return;
}
- if (lang.i18n === "hardcore") {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- setDefinition({} as any);
- return;
- }
-
import(`../../external/lang/${lang.i18n}.json`).then(
async (lang_file) => {
const defn = transformLanguage(lang_file.default);
diff --git a/src/pages/friends/Friend.module.scss b/src/pages/friends/Friend.module.scss
index d6338774..ee9c6f90 100644
--- a/src/pages/friends/Friend.module.scss
+++ b/src/pages/friends/Friend.module.scss
@@ -14,30 +14,6 @@
padding: 0 10px 10px 10px;
user-select: none;
overflow-y: scroll;
-
- summary {
- position: sticky;
- z-index: 10;
- top: -1px;
- }
-
- .overline {
- display: flex;
- align-items: center;
- background: var(--primary-background);
- padding: 5px 0;
- cursor: pointer;
-
- .title {
- text-overflow: ellipsis;
- overflow: hidden;
- white-space: nowrap;
- }
-
- svg {
- margin-inline-end: 4px;
- }
- }
&[data-empty="true"] {
img {
diff --git a/src/pages/friends/Friends.tsx b/src/pages/friends/Friends.tsx
index 284641e7..01d36085 100644
--- a/src/pages/friends/Friends.tsx
+++ b/src/pages/friends/Friends.tsx
@@ -15,6 +15,7 @@ import { UserDetail, MessageAdd, UserPlus } from "@styled-icons/boxicons-solid";
import { TextReact } from "../../lib/i18n";
import { Children } from "../../types/Preact";
import Details from "../../components/ui/Details";
+import CollapsibleSection from "../../components/common/CollapsibleSection";
export default function Friends() {
const { openScreen } = useIntermediate();
@@ -30,17 +31,17 @@ export default function Friends() {
) ],
[ 'app.special.friends.sent', users.filter(x =>
x.relationship === Users.Relationship.Outgoing
- ) ],
+ ), 'outgoing' ],
[ 'app.status.online', friends.filter(x =>
x.online && x.status?.presence !== Users.Presence.Invisible
- ) ],
+ ), 'online' ],
[ 'app.status.offline', friends.filter(x =>
!x.online || x.status?.presence === Users.Presence.Invisible
- ) ],
- [ 'app.special.friends.blocked', friends.filter(x =>
+ ), 'offline' ],
+ [ 'app.special.friends.blocked', users.filter(x =>
x.relationship === Users.Relationship.Blocked
- ) ]
- ] as [ string, User[] ][];
+ ), 'blocked' ]
+ ] as [ string, User[], string ][];
const incoming = lists[0][1];
const userlist: Children[] = incoming.map(x => { x.username });
@@ -108,22 +109,18 @@ export default function Friends() {
}
{
- lists.map(([i18n, list], index) => {
+ lists.map(([i18n, list, section_id], index) => {
if (index === 0) return;
if (list.length === 0) return;
return (
-
-
-
-
-
- — { list.length }
-
-
-
+ — { list.length }}>
{ list.map(x => ) }
-
+
)
})
}
diff --git a/src/pages/settings/panes/Appearance.tsx b/src/pages/settings/panes/Appearance.tsx
index b0ed1dae..6ddd2dc5 100644
--- a/src/pages/settings/panes/Appearance.tsx
+++ b/src/pages/settings/panes/Appearance.tsx
@@ -22,6 +22,7 @@ import mutantSVG from '../assets/mutant_emoji.svg';
import notoSVG from '../assets/noto_emoji.svg';
import openmojiSVG from '../assets/openmoji_emoji.svg';
import twemojiSVG from '../assets/twemoji_emoji.svg';
+import CollapsibleSection from "../../../components/common/CollapsibleSection";
interface Props {
settings: Settings;
@@ -171,11 +172,7 @@ export function Component(props: Props & WithDispatcher) {
-
-
-
-
-
+ }>
@@ -272,7 +269,7 @@ export function Component(props: Props & WithDispatcher) {
code
value={css}
onChange={ev => setCSS(ev.currentTarget.value)} />
-
+
);
}
diff --git a/src/redux/index.ts b/src/redux/index.ts
index dc90bec0..4bee0dcd 100644
--- a/src/redux/index.ts
+++ b/src/redux/index.ts
@@ -14,6 +14,7 @@ import { QueuedMessage } from "./reducers/queue";
import { ExperimentOptions } from "./reducers/experiments";
import { LastOpened } from "./reducers/last_opened";
import { Notifications } from "./reducers/notifications";
+import { SectionToggle } from "./reducers/section_toggle";
export type State = {
config: Core.RevoltNodeConfiguration,
@@ -28,6 +29,7 @@ export type State = {
experiments: ExperimentOptions;
lastOpened: LastOpened;
notifications: Notifications;
+ sectionToggle: SectionToggle;
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -56,7 +58,8 @@ store.subscribe(() => {
sync,
experiments,
lastOpened,
- notifications
+ notifications,
+ sectionToggle
} = store.getState() as State;
localForage.setItem("state", {
@@ -70,6 +73,7 @@ store.subscribe(() => {
sync,
experiments,
lastOpened,
- notifications
+ notifications,
+ sectionToggle
});
});
diff --git a/src/redux/reducers/index.ts b/src/redux/reducers/index.ts
index fe47ccbd..baf217dc 100644
--- a/src/redux/reducers/index.ts
+++ b/src/redux/reducers/index.ts
@@ -13,6 +13,7 @@ import { sync, SyncAction } from "./sync";
import { experiments, ExperimentsAction } from "./experiments";
import { lastOpened, LastOpenedAction } from "./last_opened";
import { notifications, NotificationsAction } from "./notifications";
+import { sectionToggle, SectionToggleAction } from "./section_toggle";
export default combineReducers({
config,
@@ -26,7 +27,8 @@ export default combineReducers({
sync,
experiments,
lastOpened,
- notifications
+ notifications,
+ sectionToggle
});
export type Action =
@@ -42,6 +44,7 @@ export type Action =
| ExperimentsAction
| LastOpenedAction
| NotificationsAction
+ | SectionToggleAction
| { type: "__INIT"; state: State };
export type WithDispatcher = { dispatcher: (action: Action) => void };
diff --git a/src/redux/reducers/section_toggle.ts b/src/redux/reducers/section_toggle.ts
new file mode 100644
index 00000000..26b23dca
--- /dev/null
+++ b/src/redux/reducers/section_toggle.ts
@@ -0,0 +1,37 @@
+export interface SectionToggle {
+ [key: string]: boolean
+}
+
+export type SectionToggleAction =
+ | { type: undefined }
+ | {
+ type: "SECTION_TOGGLE_SET";
+ id: string;
+ state: boolean;
+ }
+ | {
+ type: "SECTION_TOGGLE_UNSET";
+ id: string;
+ }
+ | {
+ type: "RESET";
+ };
+
+export function sectionToggle(state = {} as SectionToggle, action: SectionToggleAction): SectionToggle {
+ switch (action.type) {
+ case "SECTION_TOGGLE_SET": {
+ return {
+ ...state,
+ [action.id]: action.state
+ }
+ }
+ case "SECTION_TOGGLE_UNSET": {
+ const { [action.id]: _, ...newState } = state;
+ return newState;
+ }
+ case "RESET":
+ return {};
+ default:
+ return state;
+ }
+}