Skip to content

Commit b798633

Browse files
authored
Merge pull request #72 from sheodox/modlog
Modlog
2 parents c3b2462 + ce3ecd2 commit b798633

33 files changed

+1346
-110
lines changed

package-lock.json

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "Alexandrite",
3-
"version": "0.8.3",
3+
"version": "0.8.4",
44
"private": true,
55
"scripts": {
66
"dev": "vite dev",
@@ -29,7 +29,7 @@
2929
"prettier": "^3.0.0",
3030
"prettier-plugin-svelte": "^3.0.0",
3131
"sass": "^1.64.1",
32-
"sheodox-ui": "^0.20.1",
32+
"sheodox-ui": "^0.20.2",
3333
"svelte": "^4.1.1",
3434
"svelte-check": "^3.4.6",
3535
"tslib": "^2.6.1",

src/lib/ChainButtons.svelte

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<!-- we shouldn't be showing enough to wrap if our calculations work,
2+
but using f-wrap so the element doesn't overflow the container
3+
and make us show every button all the time -->
4+
<div class="chain-buttons f-row f-1 gap-2 f-wrap" use:observeContainer>
5+
{#each outside as action, index}
6+
<div use:observeOutside={{ index }}>
7+
<ExtraActionButton {cl} {action} />
8+
</div>
9+
{/each}
10+
<!-- if we haven't measured its width yet show the overflow anyway -->
11+
{#if overflowWidth === null || collapsed.length}
12+
<div class="chain-overflow" use:observeOverflow>
13+
<!-- when we don't know the size yet, give it all actions so it shows instead of hiding itself -->
14+
<ExtraActions actions={overflowWidth !== null ? collapsed : actions} {cl} />
15+
</div>
16+
{/if}
17+
</div>
18+
19+
<script lang="ts">
20+
import type { ExtraAction } from './utils';
21+
import ExtraActions from './ExtraActions.svelte';
22+
import ExtraActionButton from './ExtraActionButton.svelte';
23+
24+
export let actions: ExtraAction[];
25+
export let cl = '';
26+
27+
let outsideAmount = Infinity;
28+
let availableWidth: number | null = null;
29+
let overflowWidth: number | null = null;
30+
// map of action indexes to button widths
31+
const actionSizes = new Map<number, number>();
32+
33+
$: outside = actions.slice(0, outsideAmount);
34+
$: collapsed = actions.slice(outsideAmount);
35+
36+
const buttonGap = 8; //.gap-2 is 8px wide, this is the spacing between buttons
37+
38+
function fit() {
39+
let outsideWidth = 0,
40+
canFit = -1;
41+
42+
if (availableWidth === null || overflowWidth === null) {
43+
return;
44+
}
45+
46+
for (let i = 0; i < actions.length; i++) {
47+
outsideWidth += (actionSizes.get(i) ?? 0) + (i > 0 ? buttonGap : 0);
48+
const hasMoreActionsAfterThis = i + 1 < actions.length;
49+
50+
if (outsideWidth + (hasMoreActionsAfterThis ? overflowWidth + buttonGap : 0) < availableWidth) {
51+
canFit = i;
52+
} else {
53+
break;
54+
}
55+
}
56+
57+
outsideAmount = canFit + 1;
58+
}
59+
60+
function watchSize(el: HTMLElement, updateFn: (width: number) => void) {
61+
const obs = new ResizeObserver((entries) => {
62+
const width = entries[0].borderBoxSize[0].inlineSize;
63+
updateFn(width);
64+
65+
fit();
66+
});
67+
68+
obs.observe(el);
69+
70+
return {
71+
destroy: () => {
72+
obs.disconnect();
73+
}
74+
};
75+
}
76+
77+
function observeContainer(el: HTMLElement) {
78+
return watchSize(el, (width) => {
79+
availableWidth = width;
80+
});
81+
}
82+
83+
function observeOutside(el: HTMLElement, { index }: { index: number }) {
84+
return watchSize(el, (width) => {
85+
actionSizes.set(index, width);
86+
});
87+
}
88+
89+
function observeOverflow(el: HTMLElement) {
90+
return watchSize(el, (width) => {
91+
overflowWidth = width;
92+
});
93+
}
94+
</script>

src/lib/CommunityLink.svelte

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
<Tooltip>
3333
<div slot="tooltip" class="community-tooltip">
3434
<Stack gap={2} dir="r" align="center">
35-
{#if community.icon && $profile.settings.show_avatars}
35+
{#if community.icon && showIcon}
3636
<div class="community-avatar large">
3737
<Image src={community.icon} mode="thumbnail" />
3838
</div>
@@ -46,20 +46,22 @@
4646
{/if}
4747
</div>
4848
<span class="f-row gap-1 align-items-center">
49-
{#if community.icon && $profile.settings.show_avatars}
49+
{#if community.icon && showIcon}
5050
<div class="community-avatar small">
5151
<Image src={community.icon} mode="thumbnail" thumbnailResolution={16} />
5252
</div>
5353
{/if}
5454
<EllipsisText
5555
><NameAtInstance place={community} displayName={community.title} prefix="" wrappable={false} /></EllipsisText
5656
>
57-
<CommunityBadges {community} />
57+
{#if showBadges}
58+
<CommunityBadges {community} />
59+
{/if}
5860
</span>
5961
</Tooltip>
6062
{:else}
6163
<Stack gap={2} dir="r" align="center" cl="icon-link">
62-
{#if $profile.settings.show_avatars}
64+
{#if $profile.settings.show_avatars && showIcon}
6365
<Avatar src={community.icon} size="2rem" icon="users" />
6466
{/if}
6567
<span>
@@ -79,13 +81,25 @@
7981
import NameAtInstance from './NameAtInstance.svelte';
8082
import EllipsisText from './EllipsisText.svelte';
8183
import { profile } from './profiles/profiles';
84+
import { getSettingsContext } from './settings-context';
8285
8386
export let community: Community;
8487
export let inlineLink = true;
8588
// if the link should actually go somewhere else, but still have community "branding", use that link instead.
8689
// this is used for links to crossposts, where the community should be the visible part of the link, but the
8790
// link should actually go to the post in that community
8891
export let href: string | null = null;
92+
export let showBadges = true;
93+
94+
const { nsfwImageHandling } = getSettingsContext();
8995
9096
$: communityName = nameAtInstance(community);
97+
98+
// hide community avatars if the community is nsfw and the user doesn't want to explicitly see nsfw content,
99+
// as the community avatar often will also be nsfw
100+
$: nsfwShowable = !community.nsfw || $nsfwImageHandling === 'SHOW';
101+
102+
// just in case, don't show icons for communities that were removed for some reason
103+
$: showIcon =
104+
$profile.settings.show_avatars && community.icon && !community.deleted && !community.removed && nsfwShowable;
91105
</script>

src/lib/CommunitySidebar.svelte

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<article>
22
<Sidebar description={community.description ?? ''} bannerImageSrc={community.banner} context="Community">
3-
<a href="/{$profile.instance}/c/{nameAtInstance(community)}" slot="name">
3+
<a href={communityHref} slot="name">
44
<NameAtInstance place={community} prefix="!" />
55
</a>
66

@@ -14,20 +14,28 @@
1414
<Icon icon="arrow-up-right-from-square" />
1515
View on {communityInstance}
1616
</ExternalLink>
17+
<ModlogLink
18+
communityId={community.id}
19+
highlight={userModerates ?? false}
20+
highlightColor="green"
21+
warn={warnModlog}
22+
/>
1723
</Stack>
1824
</Stack>
1925
</div>
2026
<div slot="end">
21-
{#if moderators}
22-
<Accordion buttonClasses="tertiary">
23-
<span slot="title">Moderators ({moderators.length})</span>
24-
<Stack dir="c" gap={2}>
25-
{#each moderators as mod}
26-
<UserLink user={mod.moderator} />
27-
{/each}
28-
</Stack>
29-
</Accordion>
30-
{/if}
27+
<Stack dir="c" gap={2}>
28+
{#if moderators}
29+
<Accordion buttonClasses="tertiary">
30+
<span slot="title">Moderators ({moderators.length})</span>
31+
<Stack dir="c" gap={2}>
32+
{#each moderators as mod}
33+
<UserLink user={mod.moderator} />
34+
{/each}
35+
</Stack>
36+
</Accordion>
37+
{/if}
38+
</Stack>
3139
</div>
3240
</Sidebar>
3341
</article>
@@ -37,14 +45,24 @@
3745
import Sidebar from '$lib/Sidebar.svelte';
3846
import NameAtInstance from '$lib/NameAtInstance.svelte';
3947
import CommunityCounts from './CommunityCounts.svelte';
48+
import ModlogLink from './ModlogLink.svelte';
4049
import UserLink from './UserLink.svelte';
4150
import type { CommunityView, Community, CommunityModeratorView } from 'lemmy-js-client';
4251
import { nameAtInstance } from './nav-utils';
4352
import { profile } from '$lib/profiles/profiles';
53+
import { getAppContext } from './app-context';
54+
import { getSettingsContext } from './settings-context';
4455
4556
export let community: Community;
4657
export let communityView: CommunityView | null = null;
4758
export let moderators: CommunityModeratorView[] | null = null;
4859
60+
const { siteMeta } = getAppContext();
61+
const { showModlogWarning, showModlogWarningModerated } = getSettingsContext();
62+
63+
$: communityHref = `/${$profile.instance}/c/${nameAtInstance(community)}`;
64+
$: userModerates = $siteMeta.my_user?.moderates.some((moderates) => moderates.community.id === community.id);
65+
$: warnModlog = userModerates ? $showModlogWarningModerated : $showModlogWarning;
66+
4967
$: communityInstance = new URL(community.actor_id).host;
5068
</script>

src/lib/ExtraActionButton.svelte

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<style>
2+
.spinner {
3+
/* match the width of icons in dropdown menus from sheodox-ui */
4+
width: 1.5rem;
5+
}
6+
</style>
7+
8+
{#if action.href}
9+
<a href={action.href} class="button m-0 ws-nowrap {cl}"><Icon icon={action.icon} {variant} /> {action.text}</a>
10+
{:else if action.click}
11+
<button on:click={action.click} class="button m-0 ws-nowrap {cl}" disabled={action.busy ?? false} use:ripple>
12+
<div class="f-row gap-1">
13+
{#if action.busy}
14+
<div class="spinner">
15+
<Spinner />
16+
</div>
17+
{:else}
18+
<Icon icon={action.icon} {variant} />
19+
{/if}
20+
<span>{action.text}</span>
21+
</div>
22+
</button>
23+
{/if}
24+
25+
<script lang="ts">
26+
import type { ExtraAction } from './utils';
27+
import Spinner from './Spinner.svelte';
28+
import { Icon, ripple } from 'sheodox-ui';
29+
30+
export let action: ExtraAction;
31+
export let cl = '';
32+
33+
$: variant = action.variant || 'solid';
34+
</script>

src/lib/ExtraActions.svelte

Lines changed: 6 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
11
<style>
2-
ul .spinner {
3-
/* match the width of icons in dropdown menus from sheodox-ui */
4-
width: 1.5rem;
5-
}
62
.extra-actions-trigger {
73
aspect-ratio: 1;
84
display: block;
@@ -13,32 +9,16 @@
139
{#if actions.length}
1410
{@const text = 'Extra actions'}
1511
<Tooltip title={text}>
16-
<MenuButton triggerClasses="small m-0">
12+
<MenuButton triggerClasses="{small ? 'small' : ''} m-0 {cl}">
1713
<span slot="trigger" class="extra-actions-trigger">
1814
<span class="sr-only">{text}</span>
1915
<Icon icon="ellipsis-vertical" />
2016
</span>
2117

2218
<ul slot="menu">
2319
{#each actions as opt}
24-
{@const variant = opt.variant || 'solid'}
2520
<li>
26-
{#if opt.href}
27-
<a href={opt.href} class="button"><Icon icon={opt.icon} {variant} /> {opt.text}</a>
28-
{:else if opt.click}
29-
<button on:click={opt.click} class="button" disabled={opt.busy ?? false} use:ripple>
30-
<div class="f-row gap-1">
31-
{#if opt.busy}
32-
<div class="spinner">
33-
<Spinner />
34-
</div>
35-
{:else}
36-
<Icon icon={opt.icon} {variant} />
37-
{/if}
38-
<span>{opt.text}</span>
39-
</div>
40-
</button>
41-
{/if}
21+
<ExtraActionButton action={opt} />
4222
</li>
4323
{/each}
4424
</ul>
@@ -47,9 +27,11 @@
4727
{/if}
4828

4929
<script lang="ts">
50-
import { Tooltip, MenuButton, Icon, ripple } from 'sheodox-ui';
30+
import { Tooltip, MenuButton, Icon } from 'sheodox-ui';
5131
import type { ExtraAction } from './utils';
52-
import Spinner from './Spinner.svelte';
32+
import ExtraActionButton from './ExtraActionButton.svelte';
5333
5434
export let actions: ExtraAction[];
35+
export let small = false;
36+
export let cl = '';
5537
</script>

0 commit comments

Comments
 (0)