diff --git a/package-lock.json b/package-lock.json index e148170..a5a5282 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,16 @@ { "name": "Alexandrite", - "version": "0.8.14", + "version": "0.8.15", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "Alexandrite", - "version": "0.8.14", + "version": "0.8.15", "dependencies": { "@fortawesome/fontawesome-free": "^6.4.2", "date-fns": "^2.30.0", - "lemmy-js-client": "^0.19.0-rc.19", + "lemmy-js-client": "^0.19.6", "markdown-it": "^13.0.2", "markdown-it-container": "^3.0.0", "markdown-it-footnote": "^3.0.3", @@ -33,7 +33,7 @@ "prettier": "^3.0.3", "prettier-plugin-svelte": "^3.0.3", "sass": "^1.69.4", - "sheodox-ui": "^0.20.4", + "sheodox-ui": "^0.20.6", "svelte": "^4.2.2", "svelte-check": "^3.5.2", "tslib": "^2.6.2", @@ -1428,11 +1428,6 @@ "node": "*" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, "node_modules/axobject-query": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", @@ -1636,17 +1631,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -1668,14 +1652,6 @@ "node": ">= 0.6" } }, - "node_modules/cross-fetch": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", - "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", - "dependencies": { - "node-fetch": "^2.6.11" - } - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -1781,14 +1757,6 @@ "node": ">=0.10.0" } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -2230,19 +2198,6 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", "dev": true }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -2603,13 +2558,10 @@ "dev": true }, "node_modules/lemmy-js-client": { - "version": "0.19.0-rc.19", - "resolved": "https://registry.npmjs.org/lemmy-js-client/-/lemmy-js-client-0.19.0-rc.19.tgz", - "integrity": "sha512-kae8V33QixbyYIA+pn7+sCeOOWL3eRgnFaNkZU8Y8vxhwZExIhkZus9jzVt/BoyPddVlafyBHxgAUsWXLA4tRA==", - "dependencies": { - "cross-fetch": "^3.1.5", - "form-data": "^4.0.0" - } + "version": "0.19.6", + "resolved": "https://registry.npmjs.org/lemmy-js-client/-/lemmy-js-client-0.19.6.tgz", + "integrity": "sha512-5bs9NfjKcXVFJE3goHvu0Uo3u6+CMyQsccT2XCJyp0JExtNZM3F9oBl3t1/wiOMMGyNKiDJ+mCC9Y9gXwa1/iw==", + "license": "AGPL-3.0" }, "node_modules/levn": { "version": "0.4.1", @@ -2796,25 +2748,6 @@ "node": ">=8.6" } }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -2928,25 +2861,6 @@ "tslib": "^2.0.3" } }, - "node_modules/node-fetch": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz", - "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -3548,9 +3462,9 @@ } }, "node_modules/sheodox-ui": { - "version": "0.20.4", - "resolved": "https://registry.npmjs.org/sheodox-ui/-/sheodox-ui-0.20.4.tgz", - "integrity": "sha512-By2eY60HCitxfDZQvEJznZ65GNBdDsMrfDuLKi4Xa3SXa/FuQaKJs+0Wp13/+fYB6/kljJzRiNdPqyToLAeY4Q==", + "version": "0.20.6", + "resolved": "https://registry.npmjs.org/sheodox-ui/-/sheodox-ui-0.20.6.tgz", + "integrity": "sha512-lAiyYT8R/zdd/QUo+1bbAN+L1Zs4AaXnC8Q8NZhoQpXelHu3IfEpHb8BIYpdnsD+BY0ehKv9dXTmoxhc3+rEnQ==", "dev": true, "license": "ISC", "dependencies": { @@ -3964,11 +3878,6 @@ "node": ">=6" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, "node_modules/ts-api-utils": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.1.tgz", @@ -4242,20 +4151,6 @@ } } }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 8189bd2..5bb4817 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Alexandrite", - "version": "0.8.14", + "version": "0.8.15", "private": true, "scripts": { "dev": "vite dev --host 0.0.0.0", @@ -29,7 +29,7 @@ "prettier": "^3.0.3", "prettier-plugin-svelte": "^3.0.3", "sass": "^1.69.4", - "sheodox-ui": "^0.20.4", + "sheodox-ui": "^0.20.6", "svelte": "^4.2.2", "svelte-check": "^3.5.2", "tslib": "^2.6.2", @@ -41,7 +41,7 @@ "dependencies": { "@fortawesome/fontawesome-free": "^6.4.2", "date-fns": "^2.30.0", - "lemmy-js-client": "^0.19.0-rc.19", + "lemmy-js-client": "^0.19.6", "markdown-it": "^13.0.2", "markdown-it-container": "^3.0.0", "markdown-it-footnote": "^3.0.3", diff --git a/src/lib/CommunitySidebar.svelte b/src/lib/CommunitySidebar.svelte index 81336a8..96d1c5a 100644 --- a/src/lib/CommunitySidebar.svelte +++ b/src/lib/CommunitySidebar.svelte @@ -1,6 +1,11 @@
{#if community && communityView} - + @@ -10,32 +15,43 @@ {#if communityView} {/if} - - - - View on {communityInstance} - -
- + + Community Details +
    + {#each communityStats as stat} +
  • + {stat.label} + {stat.value.toLocaleString()} +
  • + {/each} +
  • + +
  • +
  • + + + View on {communityInstance} + +
  • +
+ {#if moderators} - - Moderation + + Moderators - -

Moderators

- -
-
    +
      {#each moderators as mod} - +
    • + +
    • {/each}
    {#if userModerates && !userIsHeadMod} @@ -48,7 +64,7 @@ {/if} - +
{/if} @@ -69,6 +85,7 @@ import { getCommunityContext } from './community-context/community-context'; import { getModActionPending, getModContext } from './mod/mod-context'; import { readable } from 'svelte/store'; + import { localStorageBackedStore } from './utils'; export let communityName: string; @@ -81,6 +98,43 @@ $: userModerates = moderators?.some((mod) => mod.moderator.id === $siteMeta.my_user?.local_user_view.person.id); $: userIsHeadMod = moderators?.[0]?.moderator.id === $userId; $: warnModlog = userModerates ? $showModlogWarningModerated : $showModlogWarning; + $: communityStats = [ + { + label: 'Posts', + value: communityView?.counts.posts ?? 0, + icon: 'file-lines' + }, + { + label: 'Comments', + value: communityView?.counts.comments ?? 0, + icon: 'comments' + }, + { + label: 'Subscribers', + value: communityView?.counts.subscribers ?? 0, + icon: 'users' + }, + { + label: 'Daily Active Users', + value: communityView?.counts.users_active_day ?? 0, + icon: 'users' + }, + { + label: 'Weekly Active Users', + value: communityView?.counts.users_active_week ?? 0, + icon: 'users' + }, + { + label: 'Montly Active Users', + value: communityView?.counts.users_active_month ?? 0, + icon: 'users' + }, + { + label: 'Half-Year Active Users', + value: communityView?.counts.users_active_half_year ?? 0, + icon: 'users' + } + ]; $: communityInstance = community ? new URL(community.actor_id).host : null; @@ -89,6 +143,9 @@ const { hasModSeniority } = getCommunityContext(); const modContext = getModContext(); + const descriptionOpen = localStorageBackedStore('community-sidebar-description-open', true); + const communityDetailsOpen = localStorageBackedStore('community-sidebar-community-details-open', true); + const userModResignPending = community ? getModActionPending('add-mod', `${community.id}-${userId}`) : readable(false); diff --git a/src/lib/PersonCard.svelte b/src/lib/PersonCard.svelte index d9dd3c3..386e999 100644 --- a/src/lib/PersonCard.svelte +++ b/src/lib/PersonCard.svelte @@ -27,9 +27,11 @@ - - - +
+
    + +
+
diff --git a/src/lib/PostCompose.svelte b/src/lib/PostCompose.svelte index c4e3ca4..c4c2dfc 100644 --- a/src/lib/PostCompose.svelte +++ b/src/lib/PostCompose.svelte @@ -54,7 +54,7 @@ import BusyButton from './BusyButton.svelte'; import LanguageSelector from './LanguageSelector.svelte'; import { getCommunityContext } from './community-context/community-context'; - import type { Community, Post, PostView, SiteMetadata } from 'lemmy-js-client'; + import type { Community, Post, PostView, LinkMetadata } from 'lemmy-js-client'; import { onMount } from 'svelte'; import { createStatefulAction, safeUrl } from './utils'; import { profile } from './profiles/profiles'; @@ -70,7 +70,7 @@ export let crossPost: PostView | null = null; export let nsfw = false; - let urlMetadata: SiteMetadata | null = null; + let urlMetadata: LinkMetadata | null = null; $: client = $profile.client; diff --git a/src/lib/Sidebar.svelte b/src/lib/Sidebar.svelte index 48bdade..b264d7a 100644 --- a/src/lib/Sidebar.svelte +++ b/src/lib/Sidebar.svelte @@ -37,13 +37,15 @@ {#if description} - {/if} - - {#if sidebar} - + {/if} +

{/if} @@ -51,7 +53,7 @@
diff --git a/src/lib/UserCounts.svelte b/src/lib/UserCounts.svelte index c328fc4..e2c5b09 100644 --- a/src/lib/UserCounts.svelte +++ b/src/lib/UserCounts.svelte @@ -1,14 +1,11 @@ - - {#each counts as count} - - {count.count.toLocaleString()} - {count.label} - - {/each} - +{#each counts as count} +
  • + {count.label} + {count.count.toLocaleString()} +
  • +{/each} diff --git a/src/routes/(app)/[instance]/instances/+page.ts b/src/routes/(app)/[instance]/instances/+page.ts new file mode 100644 index 0000000..96533c2 --- /dev/null +++ b/src/routes/(app)/[instance]/instances/+page.ts @@ -0,0 +1,11 @@ +import { get } from 'svelte/store'; +import type { PageLoad } from './$types'; +import { profile } from '$lib/profiles/profiles'; + +export const load = (async () => { + const { client } = get(profile); + + return { + federatedInstances: client.getFederatedInstances() + }; +}) satisfies PageLoad; diff --git a/src/routes/(app)/[instance]/legal/+page.svelte b/src/routes/(app)/[instance]/legal/+page.svelte new file mode 100644 index 0000000..4550055 --- /dev/null +++ b/src/routes/(app)/[instance]/legal/+page.svelte @@ -0,0 +1,14 @@ + +

    {$profile.instance} Legal

    +
    + +
    + + diff --git a/src/routes/(app)/[instance]/u/[username]/+page.svelte b/src/routes/(app)/[instance]/u/[username]/+page.svelte index 3c9b87b..0999c1d 100644 --- a/src/routes/(app)/[instance]/u/[username]/+page.svelte +++ b/src/routes/(app)/[instance]/u/[username]/+page.svelte @@ -1,3 +1,11 @@ + + {#key data} @@ -15,34 +23,70 @@ {loadingContentFailed} > <div slot="sidebar"> - <Stack dir="c" gap={2}> - <article> - <h1>Stats</h1> - <UserCounts personView={data.personView} /> - + <Stack dir="c" gap={4}> + <h1 class="m-0"> + <NameAtInstance + place={data.personView.person} + displayName={data.personView.person.display_name} + prefix="@" + /> + </h1> + + {#if isMe} + <a class="button secondary w-100 m-0 text-align-center mb-4" href="/{$profile.instance}/settings/lemmy"> + <Icon icon="edit" /> Edit Profile + </a> + {/if} + <article class="f-column gap-4"> {#if data.personView.person.bio} - <Fieldset legend="Bio" fieldsetClasses="m-0 mt-3"> - <Markdown md={data.personView.person.bio} /> - </Fieldset> + <Accordion bind:open={$userBioOpen}> + <span slot="title"><Icon icon="circle-user" /> Bio</span> + <div> + <div class="bio p-2"> + <Markdown md={data.personView.person.bio} /> + </div> + </div> + </Accordion> {/if} - </article> - <ModlogLink - highlight={false} - highlightColor={'gray'} - warn={$showModlogWarning} - label="Modlog (actions on this user)" - targetId={data.personView.person.id} - /> + <Accordion bind:open={$userStatsOpen}> + <span slot="title"><Icon icon="chart-simple" /> Stats</span> + <ul class="sx-list"> + <UserCounts personView={data.personView} /> + + <li class="sx-list-item"> + <ModlogLink + highlight={false} + highlightColor={'gray'} + warn={$showModlogWarning} + label="Modlog (actions on this user)" + targetId={data.personView.person.id} + /> + </li> + <!-- probably unnecessary to check this, but just in case --> + {#if data.personView.person.actor_id.startsWith('http')} + <li class="sx-list-item"> + <ExternalLink href={data.personView.person.actor_id} cl="inline-link" + ><Icon icon="arrow-up-right-from-square" /> Original Profile</ExternalLink + > + </li> + {/if} + </ul> + </Accordion> + </article> {#if data.moderates && data.moderates.length} <article> - <h2 class="mb-0">Moderates</h2> - <Stack dir="c" gap={2}> - {#each data.moderates as mod} - <CommunityLink community={mod.community} /> - {/each} - </Stack> + <Accordion bind:open={$userModeratesOpen}> + <span slot="title"><Icon icon="user-shield" /> Moderates</span> + <ul class="sx-list"> + {#each data.moderates as mod} + <li class="sx-list-item"> + <CommunityLink community={mod.community} /> + </li> + {/each} + </ul> + </Accordion> </article> {/if} </Stack> @@ -54,10 +98,11 @@ {/key} <script lang="ts"> - import { Stack, Fieldset } from 'sheodox-ui'; + import { Accordion, Stack, ExternalLink, Icon } from 'sheodox-ui'; import PostsPage from '$lib/feeds/posts/PostsPage.svelte'; import UserCounts from '$lib/UserCounts.svelte'; import Markdown from '$lib/Markdown.svelte'; + import NameAtInstance from '$lib/NameAtInstance.svelte'; import CommunityLink from '$lib/CommunityLink.svelte'; import ContentViewProvider from '$lib/ContentViewProvider.svelte'; import { userFeedLoader } from '$lib/post-loader.js'; @@ -67,17 +112,25 @@ import { createContentViewStore, type ContentView } from '$lib/content-views'; import ModlogLink from '$lib/ModlogLink.svelte'; import { getSettingsContext } from '$lib/settings-context'; + import { profile } from '$lib/profiles/profiles'; + import { localStorageBackedStore } from '$lib/utils'; export let data; const { showModlogWarning } = getSettingsContext(); const cvStore = createContentViewStore(); + const userBioOpen = localStorageBackedStore('user-page-sidebar-bio-open', true); + const userStatsOpen = localStorageBackedStore('user-page-sidebar-stats-open', true); + const userModeratesOpen = localStorageBackedStore('user-page-sidebar-moderates-open', true); + let loader: ReturnType<typeof initFeed>; $: { refresh(data); } + $: isMe = data.personView.person.local && data.personView.person.name === $profile.username; + function refresh(data: PageData) { loader = initFeed(data); // load the first page of data