diff --git a/client/containers/App/app.scss b/client/containers/App/app.scss index d03ff45ef9..87ca06ae46 100644 --- a/client/containers/App/app.scss +++ b/client/containers/App/app.scss @@ -9,6 +9,25 @@ outline: none; flex-grow: 1; } + /* Let the pub page fill the viewport, so its grey bottom section reaches the footer instead + of leaving a white gap when the pub is shorter than the screen. Every link in the chain + has to be a column flex container for the next one to grow into it: + + #app > #main-content > #pub-container > .pub-document-component > .pub-bottom-component + + .pub-document-component (pubDocument.scss) declares `flex-grow: 1` and is a column flex container; + .pub-grid absorbs leftover space while .pub-bottom-component (pubBottom.scss) stays at content height. + These rules connect that chain by making #main-content and #pub-container flex columns. Skipped on the dashboard, which has its own + padded #main-content layout and no growing root. */ + &:not(.dashboard) #main-content { + display: flex; + flex-direction: column; + #pub-container { + display: flex; + flex-direction: column; + flex-grow: 1; + } + } .duqduq-warning { position: fixed; right: 10px; diff --git a/client/containers/Pub/PubDocument/PubBottom/pubBottom.scss b/client/containers/Pub/PubDocument/PubBottom/pubBottom.scss index 024f3bb10c..9b60e0bfec 100644 --- a/client/containers/Pub/PubDocument/PubBottom/pubBottom.scss +++ b/client/containers/Pub/PubDocument/PubBottom/pubBottom.scss @@ -2,7 +2,8 @@ .pub-bottom-component { background: #f6f4f4; - flex-grow: 1; + /* Stay at content height — .pub-grid takes the leftover viewport space instead. */ + flex-grow: 0; overflow: hidden; padding: 2em 0em; margin-top: 2em; diff --git a/client/containers/Pub/PubDocument/PubDocument.tsx b/client/containers/Pub/PubDocument/PubDocument.tsx index 34a4e49ba6..94337c0d0b 100644 --- a/client/containers/Pub/PubDocument/PubDocument.tsx +++ b/client/containers/Pub/PubDocument/PubDocument.tsx @@ -39,7 +39,7 @@ const PubDocument = () => { const { isViewingHistory } = historyData; const { communityData, scopeData, featureFlags } = usePageContext(); const pubEdgeDisplay = useFacetsQuery((F) => F.PubEdgeDisplay); - const { canEdit, canEditDraft } = scopeData.activePermissions; + const { canEdit, canEditDraft, canCreateDiscussions } = scopeData.activePermissions; const { isReviewingPub } = pubData; const mainContentRef = useRef(null); const sideContentRef = useRef(null); @@ -51,6 +51,14 @@ const PubDocument = () => { const showPubFileImport = (canEdit || canEditDraft) && !isReadOnly; + /* Only show the Comments section if there's something to read, or this reader can actually */ + /* post. Otherwise it's an empty box with sort/filter controls that do nothing — which is what */ + /* a logged-out visitor saw on a Community that limits commenting to members and contributors, */ + /* or that disabled it outright. Discussions arrive already sanitized (spam, banned authors */ + /* and draft-vs-release visibility are filtered server-side), so an empty list here means */ + /* there is nothing this reader is allowed to see. */ + const showDiscussions = canCreateDiscussions || !!pubData.discussions?.length; + if (hidePubBody) { return null; } @@ -125,6 +133,7 @@ const PubDocument = () => { ; @Column(DataType.BOOLEAN) diff --git a/types/community.ts b/types/community.ts index 873471d058..bdfed45580 100644 --- a/types/community.ts +++ b/types/community.ts @@ -15,4 +15,9 @@ export type CommunityHeaderLink = { export type Community = SerializedModel; -export type DiscussionCreationAccess = 'public' | 'contributors-members' | 'disabled'; +export const discussionCreationAccessValues = [ + 'public', + 'contributors-members', + 'disabled', +] as const; +export type DiscussionCreationAccess = (typeof discussionCreationAccessValues)[number]; diff --git a/utils/api/contracts/publicPermissions.ts b/utils/api/contracts/publicPermissions.ts index 68d5062dcf..3c5b246bef 100644 --- a/utils/api/contracts/publicPermissions.ts +++ b/utils/api/contracts/publicPermissions.ts @@ -3,6 +3,8 @@ import type { AppRouter } from '@ts-rest/core'; import { extendZodWithOpenApi } from '@anatine/zod-openapi'; import { z } from 'zod'; +import { discussionCreationAccessValues } from 'types/community'; + extendZodWithOpenApi(z); export const publicPermissionsRouter = { @@ -11,7 +13,7 @@ export const publicPermissionsRouter = { method: 'PUT', summary: 'Update who is able to create new discussions', body: z.object({ - discussionCreationAccess: z.enum(['public', 'contributors', 'members', 'disabled']), + discussionCreationAccess: z.enum(discussionCreationAccessValues), }), responses: { 200: z.object({ success: z.boolean() }), diff --git a/utils/api/schemas/community.ts b/utils/api/schemas/community.ts index 0a0da151a1..e94b575762 100644 --- a/utils/api/schemas/community.ts +++ b/utils/api/schemas/community.ts @@ -2,6 +2,8 @@ import type * as types from 'types'; import { z } from 'zod'; +import { discussionCreationAccessValues } from 'types/community'; + import { baseSchema } from '../utils/baseSchema'; import { analyticsSettingsSchema } from './analyticsSettings'; @@ -152,5 +154,5 @@ export const communityUpdateSchema = communitySchema }) .extend({ communityId: communitySchema.shape.id, - discussionCreationAccess: z.enum(['public', 'contributors-members', 'disabled']).optional(), + discussionCreationAccess: z.enum(discussionCreationAccessValues).optional(), });