@burdenoff/microfe-theculturepost 2026.1110.3 → 2026.1112.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/theculturepost/pages/stories/StoriesPage.js +59 -57
- package/dist/theculturepost/pages/stories/StoriesPage.js.map +1 -1
- package/dist/theculturepost/utils/storyVisibility.d.ts +17 -0
- package/dist/theculturepost/utils/storyVisibility.js +27 -0
- package/dist/theculturepost/utils/storyVisibility.js.map +1 -0
- package/package.json +1 -1
|
@@ -12,12 +12,14 @@ import { ErrorView as d, LoadingView as f } from "../../components/StateViews.js
|
|
|
12
12
|
import { Pagination as p } from "../../components/Pagination.js";
|
|
13
13
|
import "../../hooks/index.js";
|
|
14
14
|
import "../../context/index.js";
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
15
|
+
import { resolveStoryPermissions as m, resolveStoryStatusFilter as h, visibleStoryStatusFilters as g } from "../../utils/storyVisibility.js";
|
|
16
|
+
import { useState as _ } from "react";
|
|
17
|
+
import { BookOpen as v, Plus as y } from "lucide-react";
|
|
18
|
+
import { Fragment as b, jsx as x, jsxs as S } from "react/jsx-runtime";
|
|
19
|
+
import { Button as C, Select as w, SelectContent as T, SelectItem as E, SelectTrigger as D, SelectValue as O } from "@burdenoff/fe-libs/ui";
|
|
20
|
+
import { useSafePermissions as k } from "@burdenoff/fe-libs/shared/providers/shell";
|
|
19
21
|
// src/theculturepost/pages/stories/StoriesPage.tsx
|
|
20
|
-
var
|
|
22
|
+
var A = 12, j = [
|
|
21
23
|
{
|
|
22
24
|
value: "ALL",
|
|
23
25
|
label: "All"
|
|
@@ -47,91 +49,91 @@ var E = 12, D = [
|
|
|
47
49
|
label: "Archived"
|
|
48
50
|
}
|
|
49
51
|
];
|
|
50
|
-
function
|
|
51
|
-
let { navigateTo:
|
|
52
|
-
status:
|
|
53
|
-
featured:
|
|
54
|
-
topicId:
|
|
52
|
+
function M() {
|
|
53
|
+
let { navigateTo: M, workspaceId: N } = e(), P = o(), F = a(), I = k(), { canEdit: L, canCreate: R } = m(I, N), [z, B] = _(null), V = h(z, L), H = g(L), [U, W] = _("ALL"), [G, K] = _(!1), q = i({ pagination: { limit: 100 } }), { data: J, isLoading: Y, isError: X, refetch: Z } = r({
|
|
54
|
+
status: L ? V === "ALL" || V === "FEATURED" ? void 0 : V : "PUBLISHED",
|
|
55
|
+
featured: V === "FEATURED" || void 0,
|
|
56
|
+
topicId: U === "ALL" ? void 0 : U,
|
|
55
57
|
pagination: {
|
|
56
|
-
limit:
|
|
57
|
-
cursor:
|
|
58
|
+
limit: A,
|
|
59
|
+
cursor: F.cursor
|
|
58
60
|
}
|
|
59
|
-
}),
|
|
60
|
-
return /* @__PURE__ */
|
|
61
|
-
title:
|
|
62
|
-
description:
|
|
63
|
-
actions: /* @__PURE__ */
|
|
64
|
-
onClick: () =>
|
|
61
|
+
}), Q = J?.items ?? [];
|
|
62
|
+
return /* @__PURE__ */ S(t, {
|
|
63
|
+
title: P("stories.title", "Stories"),
|
|
64
|
+
description: P("stories.description", "Articles, essays, explainers, photo stories and timelines — across the full editorial lifecycle."),
|
|
65
|
+
actions: R && /* @__PURE__ */ S(C, {
|
|
66
|
+
onClick: () => K(!0),
|
|
65
67
|
"data-tour": "theculturepost-stories-create",
|
|
66
68
|
children: [
|
|
67
|
-
/* @__PURE__ */
|
|
69
|
+
/* @__PURE__ */ x(y, { className: "mr-1.5 h-4 w-4" }),
|
|
68
70
|
" ",
|
|
69
|
-
|
|
71
|
+
P("stories.newStory", "New story")
|
|
70
72
|
]
|
|
71
73
|
}),
|
|
72
74
|
children: [
|
|
73
|
-
/* @__PURE__ */
|
|
74
|
-
/* @__PURE__ */
|
|
75
|
+
/* @__PURE__ */ x(l, { className: "mb-6" }),
|
|
76
|
+
/* @__PURE__ */ S("div", {
|
|
75
77
|
className: "mb-6 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between",
|
|
76
78
|
"data-tour": "theculturepost-stories-filters",
|
|
77
|
-
children: [/* @__PURE__ */
|
|
78
|
-
options:
|
|
79
|
-
value:
|
|
79
|
+
children: [/* @__PURE__ */ x(n, {
|
|
80
|
+
options: j.filter((e) => H.includes(e.value)),
|
|
81
|
+
value: V,
|
|
80
82
|
onChange: (e) => {
|
|
81
|
-
|
|
83
|
+
B(e), F.reset();
|
|
82
84
|
}
|
|
83
|
-
}), /* @__PURE__ */
|
|
84
|
-
value:
|
|
85
|
+
}), /* @__PURE__ */ S(w, {
|
|
86
|
+
value: U,
|
|
85
87
|
onValueChange: (e) => {
|
|
86
|
-
|
|
88
|
+
W(e), F.reset();
|
|
87
89
|
},
|
|
88
|
-
children: [/* @__PURE__ */
|
|
90
|
+
children: [/* @__PURE__ */ x(D, {
|
|
89
91
|
className: "w-full sm:w-[13rem]",
|
|
90
92
|
"aria-label": "Filter by topic",
|
|
91
|
-
children: /* @__PURE__ */
|
|
92
|
-
}), /* @__PURE__ */
|
|
93
|
+
children: /* @__PURE__ */ x(O, { placeholder: "All topics" })
|
|
94
|
+
}), /* @__PURE__ */ S(T, { children: [/* @__PURE__ */ x(E, {
|
|
93
95
|
value: "ALL",
|
|
94
96
|
children: "All topics"
|
|
95
|
-
}), (
|
|
97
|
+
}), (q.data?.items ?? []).map((e) => /* @__PURE__ */ x(E, {
|
|
96
98
|
value: e.id,
|
|
97
99
|
children: e.name
|
|
98
100
|
}, e.id))] })]
|
|
99
101
|
})]
|
|
100
102
|
}),
|
|
101
|
-
|
|
103
|
+
Y ? /* @__PURE__ */ x(f, { label: "Loading stories…" }) : X ? /* @__PURE__ */ x(d, {
|
|
102
104
|
message: "We couldn't load stories.",
|
|
103
|
-
onRetry: () =>
|
|
104
|
-
}) :
|
|
105
|
+
onRetry: () => Z()
|
|
106
|
+
}) : Q.length === 0 && F.page === 0 ? /* @__PURE__ */ x(u, {
|
|
105
107
|
illustration: "empty-data",
|
|
106
|
-
icon: /* @__PURE__ */ v
|
|
107
|
-
title:
|
|
108
|
-
description:
|
|
109
|
-
action: /* @__PURE__ */
|
|
110
|
-
onClick: () =>
|
|
111
|
-
children: [/* @__PURE__ */
|
|
108
|
+
icon: /* @__PURE__ */ x(v, { className: "h-10 w-10" }),
|
|
109
|
+
title: V === "ALL" && U === "ALL" ? "No stories yet" : "No stories match these filters",
|
|
110
|
+
description: V === "ALL" && U === "ALL" ? "Draft the first story — it enters the editorial lifecycle you can publish from." : "Try widening the filters, or draft a new story.",
|
|
111
|
+
action: R && /* @__PURE__ */ S(C, {
|
|
112
|
+
onClick: () => K(!0),
|
|
113
|
+
children: [/* @__PURE__ */ x(y, { className: "mr-1.5 h-4 w-4" }), " New story"]
|
|
112
114
|
})
|
|
113
|
-
}) : /* @__PURE__ */
|
|
115
|
+
}) : /* @__PURE__ */ S(b, { children: [/* @__PURE__ */ x("div", {
|
|
114
116
|
className: "grid gap-5 sm:grid-cols-2 lg:grid-cols-3",
|
|
115
|
-
children:
|
|
117
|
+
children: Q.map((e) => /* @__PURE__ */ x(s, {
|
|
116
118
|
story: e,
|
|
117
|
-
onOpen: () =>
|
|
119
|
+
onOpen: () => M(`stories/${e.id}`)
|
|
118
120
|
}, e.id))
|
|
119
|
-
}), /* @__PURE__ */
|
|
120
|
-
page:
|
|
121
|
-
pageSize:
|
|
122
|
-
itemCount:
|
|
123
|
-
hasMore:
|
|
124
|
-
onPageChange: (e) =>
|
|
121
|
+
}), /* @__PURE__ */ x(p, {
|
|
122
|
+
page: F.page,
|
|
123
|
+
pageSize: A,
|
|
124
|
+
itemCount: Q.length,
|
|
125
|
+
hasMore: J?.pageInfo.hasMore ?? !1,
|
|
126
|
+
onPageChange: (e) => F.onPageChange(e, J?.pageInfo.cursor)
|
|
125
127
|
})] }),
|
|
126
|
-
/* @__PURE__ */
|
|
127
|
-
open:
|
|
128
|
-
onOpenChange:
|
|
129
|
-
topicId:
|
|
130
|
-
onCreated: (e) =>
|
|
128
|
+
/* @__PURE__ */ x(c, {
|
|
129
|
+
open: R && G,
|
|
130
|
+
onOpenChange: K,
|
|
131
|
+
topicId: U === "ALL" ? void 0 : U,
|
|
132
|
+
onCreated: (e) => M(`stories/${e.id}`)
|
|
131
133
|
})
|
|
132
134
|
]
|
|
133
135
|
});
|
|
134
136
|
}
|
|
135
|
-
export {
|
|
137
|
+
export { M as StoriesPage };
|
|
136
138
|
|
|
137
139
|
//# sourceMappingURL=StoriesPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StoriesPage.js","names":["_Fragment","Fragment$1","PAGE_SIZE","STATUS_FILTERS","StoriesPage","useTheCulturePost","tr","useTr","pager","useCursorPager","status","setStatus","useState","topicId","setTopicId","createOpen","setCreateOpen","topicsQuery","useTcpTopics","useTcpStories","stories","data","PageLayout","Button","Plus","AiReviewQueue","FilterTabs","next","Select","SelectTrigger","SelectValue","SelectContent","SelectItem","topic","isLoading","LoadingView","isError","ErrorView","refetch","EmptyState","BookOpen","StoryCard","navigateTo","story","Pagination","target","CreateStoryDialog"],"sources":["../../../../src/theculturepost/pages/stories/StoriesPage.tsx"],"sourcesContent":["/**\n * StoriesPage — browse the newsroom.\n * @module theculturepost/pages/stories\n *\n * Reads `tcpListStories` (filtered by status + optional topic). \"New Story\"\n * opens {@link CreateStoryDialog}. Cursor-paginated.\n */\n\nimport { useState } from 'react';\nimport { BookOpen, Plus } from 'lucide-react';\nimport {\n Button,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { FilterTabs, type FilterOption } from '../../components/FilterTabs';\nimport { StoryCard } from '../../components/StoryCard';\nimport { CreateStoryDialog } from '../../components/CreateStoryDialog';\nimport { AiReviewQueue } from '../../components/AiContextPanel';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { Pagination } from '../../components/Pagination';\nimport { useTcpStories, useTcpTopics, useCursorPager, useTr } from '../../hooks';\nimport { useTheCulturePost } from '../../context';\nimport type { TcpStoryStatus } from '../../types';\n\nconst PAGE_SIZE = 12;\n\ntype StatusFilter = 'ALL' | 'FEATURED' | TcpStoryStatus;\n\nconst STATUS_FILTERS: FilterOption<StatusFilter>[] = [\n { value: 'ALL', label: 'All' },\n { value: 'FEATURED', label: 'Featured' },\n { value: 'PUBLISHED', label: 'Published' },\n { value: 'IN_REVIEW', label: 'In Review' },\n { value: 'DRAFT', label: 'Draft' },\n { value: 'IDEA', label: 'Idea' },\n { value: 'ARCHIVED', label: 'Archived' },\n];\n\nexport function StoriesPage() {\n const { navigateTo } = useTheCulturePost();\n const tr = useTr();\n const pager = useCursorPager();\n const [status, setStatus] = useState<StatusFilter>('ALL');\n const [topicId, setTopicId] = useState<string>('ALL');\n const [createOpen, setCreateOpen] = useState(false);\n\n const topicsQuery = useTcpTopics({ pagination: { limit: 100 } });\n const { data, isLoading, isError, refetch } = useTcpStories({\n status: status === 'ALL' || status === 'FEATURED' ? undefined : status,\n featured: status === 'FEATURED' ? true : undefined,\n topicId: topicId === 'ALL' ? undefined : topicId,\n pagination: { limit: PAGE_SIZE, cursor: pager.cursor },\n });\n\n const stories = data?.items ?? [];\n\n const changeStatus = (next: StatusFilter) => {\n setStatus(next);\n pager.reset();\n };\n\n const changeTopic = (next: string) => {\n setTopicId(next);\n pager.reset();\n };\n\n return (\n <PageLayout\n title={tr('stories.title', 'Stories')}\n description={tr(\n 'stories.description',\n 'Articles, essays, explainers, photo stories and timelines — across the full editorial lifecycle.'\n )}\n actions={\n <Button onClick={() => setCreateOpen(true)} data-tour=\"theculturepost-stories-create\">\n <Plus className=\"mr-1.5 h-4 w-4\" /> {tr('stories.newStory', 'New story')}\n </Button>\n }\n >\n <AiReviewQueue className=\"mb-6\" />\n\n <div\n className=\"mb-6 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\"\n data-tour=\"theculturepost-stories-filters\"\n >\n <FilterTabs options={STATUS_FILTERS} value={status} onChange={changeStatus} />\n <Select value={topicId} onValueChange={changeTopic}>\n <SelectTrigger className=\"w-full sm:w-[13rem]\" aria-label=\"Filter by topic\">\n <SelectValue placeholder=\"All topics\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"ALL\">All topics</SelectItem>\n {(topicsQuery.data?.items ?? []).map((topic) => (\n <SelectItem key={topic.id} value={topic.id}>\n {topic.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n {isLoading ? (\n <LoadingView label=\"Loading stories…\" />\n ) : isError ? (\n <ErrorView message=\"We couldn't load stories.\" onRetry={() => refetch()} />\n ) : stories.length === 0 && pager.page === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<BookOpen className=\"h-10 w-10\" />}\n title={\n status === 'ALL' && topicId === 'ALL'\n ? 'No stories yet'\n : 'No stories match these filters'\n }\n description={\n status === 'ALL' && topicId === 'ALL'\n ? 'Draft the first story — it enters the editorial lifecycle you can publish from.'\n : 'Try widening the filters, or draft a new story.'\n }\n action={\n <Button onClick={() => setCreateOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> New story\n </Button>\n }\n />\n ) : (\n <>\n <div className=\"grid gap-5 sm:grid-cols-2 lg:grid-cols-3\">\n {stories.map((story) => (\n <StoryCard\n key={story.id}\n story={story}\n onOpen={() => navigateTo(`stories/${story.id}`)}\n />\n ))}\n </div>\n <Pagination\n page={pager.page}\n pageSize={PAGE_SIZE}\n itemCount={stories.length}\n hasMore={data?.pageInfo.hasMore ?? false}\n onPageChange={(target) => pager.onPageChange(target, data?.pageInfo.cursor)}\n />\n </>\n )}\n\n <CreateStoryDialog\n open={createOpen}\n onOpenChange={setCreateOpen}\n topicId={topicId === 'ALL' ? undefined : topicId}\n onCreated={(story) => navigateTo(`stories/${story.id}`)}\n />\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"StoriesPage.js","names":["_Fragment","Fragment$1","PAGE_SIZE","STATUS_FILTERS","StoriesPage","useTheCulturePost","tr","useTr","pager","useCursorPager","permissions","useSafePermissions","resolveStoryPermissions","workspaceId","selectedStatus","setStatus","useState","status","resolveStoryStatusFilter","canEdit","visibleStatuses","visibleStoryStatusFilters","topicId","setTopicId","createOpen","setCreateOpen","topicsQuery","useTcpTopics","useTcpStories","stories","data","PageLayout","canCreate","Button","Plus","AiReviewQueue","FilterTabs","filter","next","Select","SelectTrigger","SelectValue","SelectContent","SelectItem","topic","isLoading","LoadingView","isError","ErrorView","refetch","EmptyState","BookOpen","StoryCard","navigateTo","story","Pagination","target","CreateStoryDialog"],"sources":["../../../../src/theculturepost/pages/stories/StoriesPage.tsx"],"sourcesContent":["/**\n * StoriesPage — browse the newsroom.\n * @module theculturepost/pages/stories\n *\n * Reads `tcpListStories` (filtered by status + optional topic). \"New Story\"\n * opens {@link CreateStoryDialog}. Cursor-paginated.\n */\n\nimport { useState } from 'react';\nimport { useSafePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { BookOpen, Plus } from 'lucide-react';\nimport {\n Button,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { FilterTabs, type FilterOption } from '../../components/FilterTabs';\nimport { StoryCard } from '../../components/StoryCard';\nimport { CreateStoryDialog } from '../../components/CreateStoryDialog';\nimport { AiReviewQueue } from '../../components/AiContextPanel';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { Pagination } from '../../components/Pagination';\nimport { useTcpStories, useTcpTopics, useCursorPager, useTr } from '../../hooks';\nimport { useTheCulturePost } from '../../context';\nimport {\n resolveStoryStatusFilter,\n resolveStoryPermissions,\n visibleStoryStatusFilters,\n type StoryStatusFilter,\n} from '../../utils/storyVisibility';\n\nconst PAGE_SIZE = 12;\n\ntype StatusFilter = StoryStatusFilter;\n\nconst STATUS_FILTERS: FilterOption<StatusFilter>[] = [\n { value: 'ALL', label: 'All' },\n { value: 'FEATURED', label: 'Featured' },\n { value: 'PUBLISHED', label: 'Published' },\n { value: 'IN_REVIEW', label: 'In Review' },\n { value: 'DRAFT', label: 'Draft' },\n { value: 'IDEA', label: 'Idea' },\n { value: 'ARCHIVED', label: 'Archived' },\n];\n\nexport function StoriesPage() {\n const { navigateTo, workspaceId } = useTheCulturePost();\n const tr = useTr();\n const pager = useCursorPager();\n const permissions = useSafePermissions();\n const { canEdit, canCreate } = resolveStoryPermissions(permissions, workspaceId);\n const [selectedStatus, setStatus] = useState<StatusFilter | null>(null);\n const status = resolveStoryStatusFilter(selectedStatus, canEdit);\n const visibleStatuses = visibleStoryStatusFilters(canEdit);\n const [topicId, setTopicId] = useState<string>('ALL');\n const [createOpen, setCreateOpen] = useState(false);\n\n const topicsQuery = useTcpTopics({ pagination: { limit: 100 } });\n const { data, isLoading, isError, refetch } = useTcpStories({\n status: !canEdit ? 'PUBLISHED' : status === 'ALL' || status === 'FEATURED' ? undefined : status,\n featured: status === 'FEATURED' ? true : undefined,\n topicId: topicId === 'ALL' ? undefined : topicId,\n pagination: { limit: PAGE_SIZE, cursor: pager.cursor },\n });\n\n const stories = data?.items ?? [];\n\n const changeStatus = (next: StatusFilter) => {\n setStatus(next);\n pager.reset();\n };\n\n const changeTopic = (next: string) => {\n setTopicId(next);\n pager.reset();\n };\n\n return (\n <PageLayout\n title={tr('stories.title', 'Stories')}\n description={tr(\n 'stories.description',\n 'Articles, essays, explainers, photo stories and timelines — across the full editorial lifecycle.'\n )}\n actions={\n canCreate && (\n <Button onClick={() => setCreateOpen(true)} data-tour=\"theculturepost-stories-create\">\n <Plus className=\"mr-1.5 h-4 w-4\" /> {tr('stories.newStory', 'New story')}\n </Button>\n )\n }\n >\n <AiReviewQueue className=\"mb-6\" />\n\n <div\n className=\"mb-6 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\"\n data-tour=\"theculturepost-stories-filters\"\n >\n <FilterTabs\n options={STATUS_FILTERS.filter((filter) => visibleStatuses.includes(filter.value))}\n value={status}\n onChange={changeStatus}\n />\n <Select value={topicId} onValueChange={changeTopic}>\n <SelectTrigger className=\"w-full sm:w-[13rem]\" aria-label=\"Filter by topic\">\n <SelectValue placeholder=\"All topics\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"ALL\">All topics</SelectItem>\n {(topicsQuery.data?.items ?? []).map((topic) => (\n <SelectItem key={topic.id} value={topic.id}>\n {topic.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n {isLoading ? (\n <LoadingView label=\"Loading stories…\" />\n ) : isError ? (\n <ErrorView message=\"We couldn't load stories.\" onRetry={() => refetch()} />\n ) : stories.length === 0 && pager.page === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<BookOpen className=\"h-10 w-10\" />}\n title={\n status === 'ALL' && topicId === 'ALL'\n ? 'No stories yet'\n : 'No stories match these filters'\n }\n description={\n status === 'ALL' && topicId === 'ALL'\n ? 'Draft the first story — it enters the editorial lifecycle you can publish from.'\n : 'Try widening the filters, or draft a new story.'\n }\n action={\n canCreate && (\n <Button onClick={() => setCreateOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> New story\n </Button>\n )\n }\n />\n ) : (\n <>\n <div className=\"grid gap-5 sm:grid-cols-2 lg:grid-cols-3\">\n {stories.map((story) => (\n <StoryCard\n key={story.id}\n story={story}\n onOpen={() => navigateTo(`stories/${story.id}`)}\n />\n ))}\n </div>\n <Pagination\n page={pager.page}\n pageSize={PAGE_SIZE}\n itemCount={stories.length}\n hasMore={data?.pageInfo.hasMore ?? false}\n onPageChange={(target) => pager.onPageChange(target, data?.pageInfo.cursor)}\n />\n </>\n )}\n\n <CreateStoryDialog\n open={canCreate && createOpen}\n onOpenChange={setCreateOpen}\n topicId={topicId === 'ALL' ? undefined : topicId}\n onCreated={(story) => navigateTo(`stories/${story.id}`)}\n />\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { TcpStoryStatus } from '../types';
|
|
2
|
+
export type StoryStatusFilter = 'ALL' | 'FEATURED' | TcpStoryStatus;
|
|
3
|
+
interface StoryPermissionState {
|
|
4
|
+
isLoading: boolean;
|
|
5
|
+
error: Error | null;
|
|
6
|
+
scopeId: string | null;
|
|
7
|
+
hasPermission: (permission: string) => boolean;
|
|
8
|
+
}
|
|
9
|
+
/** Cached grants from another scope, or an unsuccessful refresh, are not editorial authority. */
|
|
10
|
+
export declare function resolveStoryPermissions(permissions: StoryPermissionState | null, workspaceId: string | undefined): {
|
|
11
|
+
canEdit: boolean;
|
|
12
|
+
canCreate: boolean;
|
|
13
|
+
};
|
|
14
|
+
/** Fail closed while permissions load, and immediately after an editor grant is revoked. */
|
|
15
|
+
export declare function resolveStoryStatusFilter(selected: StoryStatusFilter | null, canEdit: boolean): StoryStatusFilter;
|
|
16
|
+
export declare function visibleStoryStatusFilters(canEdit: boolean): readonly StoryStatusFilter[];
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// src/theculturepost/utils/storyVisibility.ts
|
|
2
|
+
function e(e, t) {
|
|
3
|
+
return !e || e.isLoading || e.error || !t || e.scopeId !== t ? {
|
|
4
|
+
canEdit: !1,
|
|
5
|
+
canCreate: !1
|
|
6
|
+
} : {
|
|
7
|
+
canEdit: e.hasPermission("theculturepost-story:update"),
|
|
8
|
+
canCreate: e.hasPermission("theculturepost-story:create")
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
function t(e, t) {
|
|
12
|
+
return t ? e ?? "ALL" : e === "FEATURED" ? "FEATURED" : "PUBLISHED";
|
|
13
|
+
}
|
|
14
|
+
function n(e) {
|
|
15
|
+
return e ? [
|
|
16
|
+
"ALL",
|
|
17
|
+
"FEATURED",
|
|
18
|
+
"PUBLISHED",
|
|
19
|
+
"IN_REVIEW",
|
|
20
|
+
"DRAFT",
|
|
21
|
+
"IDEA",
|
|
22
|
+
"ARCHIVED"
|
|
23
|
+
] : ["PUBLISHED", "FEATURED"];
|
|
24
|
+
}
|
|
25
|
+
export { e as resolveStoryPermissions, t as resolveStoryStatusFilter, n as visibleStoryStatusFilters };
|
|
26
|
+
|
|
27
|
+
//# sourceMappingURL=storyVisibility.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storyVisibility.js","names":["resolveStoryPermissions","permissions","workspaceId","resolveStoryStatusFilter","canEdit","selected","visibleStoryStatusFilters"],"sources":["../../../src/theculturepost/utils/storyVisibility.ts"],"sourcesContent":["import type { TcpStoryStatus } from '../types';\n\nexport type StoryStatusFilter = 'ALL' | 'FEATURED' | TcpStoryStatus;\n\ninterface StoryPermissionState {\n isLoading: boolean;\n error: Error | null;\n scopeId: string | null;\n hasPermission: (permission: string) => boolean;\n}\n\n/** Cached grants from another scope, or an unsuccessful refresh, are not editorial authority. */\nexport function resolveStoryPermissions(\n permissions: StoryPermissionState | null,\n workspaceId: string | undefined\n): { canEdit: boolean; canCreate: boolean } {\n if (\n !permissions ||\n permissions.isLoading ||\n permissions.error ||\n !workspaceId ||\n permissions.scopeId !== workspaceId\n ) {\n return { canEdit: false, canCreate: false };\n }\n return {\n canEdit: permissions.hasPermission('theculturepost-story:update'),\n canCreate: permissions.hasPermission('theculturepost-story:create'),\n };\n}\n\n/** Fail closed while permissions load, and immediately after an editor grant is revoked. */\nexport function resolveStoryStatusFilter(\n selected: StoryStatusFilter | null,\n canEdit: boolean\n): StoryStatusFilter {\n if (canEdit) return selected ?? 'ALL';\n return selected === 'FEATURED' ? 'FEATURED' : 'PUBLISHED';\n}\n\nexport function visibleStoryStatusFilters(canEdit: boolean): readonly StoryStatusFilter[] {\n return canEdit\n ? ['ALL', 'FEATURED', 'PUBLISHED', 'IN_REVIEW', 'DRAFT', 'IDEA', 'ARCHIVED']\n : ['PUBLISHED', 'FEATURED'];\n}\n"],"mappings":";AAYA,SAAgBA,EACd,GACA,GAC0C;CAU1C,OARE,CAACC,KACDA,EAAY,aACZA,EAAY,SACZ,CAACC,KACDD,EAAY,YAAYC,IAEjB;EAAE,SAAS;EAAO,WAAW;CAAM,IAErC;EACL,SAASD,EAAY,cAAc,6BAA6B;EAChE,WAAWA,EAAY,cAAc,6BAA6B;CACpE;AACF;AAGA,SAAgBE,EACd,GACA,GACmB;CAEnB,OADIC,IAAgBC,KAAY,QACzBA,MAAa,aAAa,aAAa;AAChD;AAEA,SAAgBC,EAA0B,GAAgD;CACxF,OAAOF,IACH;EAAC;EAAO;EAAY;EAAa;EAAa;EAAS;EAAQ;CAAU,IACzE,CAAC,aAAa,UAAU;AAC9B"}
|