@vonaffenfels/contentful-teasermanager 1.2.18 → 1.2.19

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.
Files changed (36) hide show
  1. package/.babelrc +19 -19
  2. package/.nvmrc +1 -1
  3. package/dist/TestStory.js +94 -0
  4. package/dist/TestStory2.js +94 -0
  5. package/dist/TestStory3.js +94 -0
  6. package/dist/index.html +11 -11
  7. package/dist/index.js +99233 -44287
  8. package/package.json +3 -3
  9. package/postcss.config.js +6 -6
  10. package/src/components/Contentful/ConfigScreen.js +154 -154
  11. package/src/components/Contentful/Dialog/LogicEditor.js +533 -533
  12. package/src/components/Contentful/Dialog/NewestArticles.js +198 -198
  13. package/src/components/Contentful/Dialog.js +87 -87
  14. package/src/components/Contentful/EntryEditor.js +196 -196
  15. package/src/components/Contentful/Page.js +9 -9
  16. package/src/components/NoAccess.js +12 -12
  17. package/src/components/Teasermanager/Timeline.js +179 -179
  18. package/src/components/Teasermanager/Timeline.module.css +89 -89
  19. package/src/components/Teasermanager.js +269 -269
  20. package/src/components/Teasermanager.module.css +137 -137
  21. package/src/dev.js +5 -5
  22. package/src/hooks/useDebounce.js +20 -20
  23. package/src/hooks/useOnScreen.js +23 -23
  24. package/src/icons/remove.svg +7 -7
  25. package/src/index.html +11 -11
  26. package/src/index.js +51 -51
  27. package/src/lib/contentfulClient.js +12 -12
  28. package/src/lib/runLoaders.js +46 -46
  29. package/src/queryFromLogic.js +33 -33
  30. package/src/scss/index.scss +11 -11
  31. package/tailwind.config.js +5 -5
  32. package/webpack.config.dev.js +25 -25
  33. package/webpack.config.js +174 -174
  34. package/dist/_base_slate-editor_src_dev_testComponents_TestStory2_js.js +0 -94
  35. package/dist/_base_slate-editor_src_dev_testComponents_TestStory3_js.js +0 -94
  36. package/dist/_base_slate-editor_src_dev_testComponents_TestStory_js.js +0 -94
@@ -1,199 +1,199 @@
1
- import {
2
- EntityList, Pagination, Spinner, Select, Autocomplete, TextInput,
3
- } from "@contentful/f36-components";
4
- import {getContentfulClient} from "../../../lib/contentfulClient";
5
- import {
6
- useEffect, useState,
7
- } from "react";
8
- import format from "date-fns/format";
9
- import useDebounce from "../../../hooks/useDebounce";
10
- import classNames from "classnames";
11
-
12
- export const NewestArticles = ({
13
- slotState = {},
14
- portals = {},
15
- sdk,
16
- onEntryClick = () => alert("onEntryClick missing"),
17
- getArticleThumbnailUrl = (article) => alert("getArticleThumbnailUrl missing"),
18
- }) => {
19
- const {
20
- portal,
21
- slotId,
22
- } = sdk.parameters.invocation;
23
- const contentfulClient = getContentfulClient();
24
- const limit = 25;
25
- const [searchQuery, setSearchQuery] = useState("");
26
- const [authorSearchQuery, setAuthorSearchQuery] = useState("");
27
- const [enabledPortals, setEnabledPortals] = useState([portal]);
28
- const [selectedPortal, setSelectedPortal] = useState(portal);
29
- const [loading, setLoading] = useState(true);
30
- const [data, setData] = useState({
31
- items: [],
32
- total: 0,
33
- });
34
- const [page, setPage] = useState(0);
35
- const [selectedAuthor, setSelectedAuthor] = useState(null);
36
- const [authors, setAuthors] = useState([]);
37
- const [authorsLoading, setAuthorsLoading] = useState(false);
38
-
39
- const debouncedSearch = useDebounce(searchQuery, 500);
40
- const debouncedAuthorSearch = useDebounce(authorSearchQuery, 500);
41
-
42
- useEffect(() => {
43
- sdk.space.getContentType("article").then(articleType => {
44
- let enabledPortals = articleType.fields.find(v => v.id === "portal")?.validations?.find(v => !!v.in)?.in;
45
-
46
- if (enabledPortals) {
47
- setEnabledPortals(enabledPortals);
48
- }
49
- });
50
- }, []);
51
-
52
- useEffect(() => {
53
- setPage(0);
54
- }, [searchQuery, selectedAuthor, selectedPortal]);
55
-
56
- useEffect(() => {
57
- setLoading(true);
58
- const params = {
59
- limit: limit,
60
- skip: page * limit,
61
- content_type: "article",
62
- order: "-fields.teaserDate",
63
- "fields.portal": selectedPortal,
64
- select: [
65
- "fields.title",
66
- "fields.authors",
67
- "fields.teaserDate",
68
- "fields.image",
69
- "fields.date",
70
- "fields.portal",
71
- "sys.publishedAt",
72
- "sys.id",
73
- ].join(","),
74
- };
75
-
76
- if (searchQuery) {
77
- params["fields.title[match]"] = searchQuery;
78
- }
79
-
80
- if (selectedAuthor) {
81
- params["fields.authors.sys.id"] = selectedAuthor;
82
- }
83
-
84
- contentfulClient.getEntries(params).then((response) => {
85
- setLoading(false);
86
- setData(response);
87
- });
88
- }, [debouncedSearch, selectedAuthor, selectedPortal, limit, page]);
89
-
90
- useEffect(() => {
91
- setAuthorsLoading(true);
92
- const params = {
93
- limit: 100,
94
- content_type: "author",
95
- order: "fields.name",
96
- "fields.portal": portal,
97
- };
98
-
99
- if (debouncedAuthorSearch) {
100
- params["fields.name[match]"] = debouncedAuthorSearch;
101
- } else {
102
- setSelectedAuthor(null);
103
- }
104
-
105
- contentfulClient.getEntries(params).then((response) => {
106
- setAuthors(response?.items || []);
107
- setAuthorsLoading(false);
108
- });
109
- }, [debouncedAuthorSearch, portal]);
110
-
111
- useEffect(() => {
112
- if (selectedAuthor) {
113
- if (!authors.find(author => author.sys.id === selectedAuthor)) {
114
- setSelectedAuthor(null);
115
- }
116
- }
117
- }, [authors, selectedAuthor]);
118
-
119
- return (
120
- <div className="flex flex-col space-y-4 p-4">
121
- <div className="flex space-x-2">
122
- <div className="grow">
123
- <TextInput
124
- width={"full"}
125
- placeholder={"Suche nach Titel"}
126
- onChange={(e) => setSearchQuery(e.target.value)}/>
127
- </div>
128
- <div className="grow-0">
129
- <Autocomplete
130
- isLoading={authorsLoading}
131
- onSelectItem={item => setSelectedAuthor(item?.sys?.id)}
132
- items={authors}
133
- closeAfterSelect={true}
134
- itemToString={(item) => item.fields?.name?.de}
135
- renderItem={(item) => `${item.fields?.name?.de || "Unbekannter Name"}`}
136
- onInputValueChange={setAuthorSearchQuery}
137
-
138
- placeholder={'Autor'}
139
- noMatchesMessage={'Kein Autor gefunden'}
140
- >
141
- {(options) => {
142
- return options.map(option => <span key={option.value}>{option.label}</span>);
143
- }}
144
- </Autocomplete>
145
- </div>
146
- <div className="grow-0">
147
- <Select value={selectedPortal} onChange={e => setSelectedPortal(e.target.value)}>
148
- {Object.keys(portals).filter(v => enabledPortals.includes(v)).sort().map(optionValue => <Select.Option
149
- key={optionValue}
150
- value={optionValue}>{portals[optionValue]}</Select.Option>)}
151
- </Select>
152
- </div>
153
- </div>
154
- <EntityList>
155
- {loading && <Spinner/>}
156
- {!loading && data.items.map((entry) => {
157
- const isCurrentlySelectedInOtherSlot = !! Object.keys(slotState || {}).find(slot => slotState[slot] === entry.sys.id && slot !== slotId);
158
- const isCurrentlySelectedInSlot = !!Object.keys(slotState || {}).find(slot => slotState[slot] === entry.sys.id && slot === slotId);
159
-
160
- let status = "draft";
161
- if (entry.sys.publishedAt) {
162
- status = "published";
163
- }
164
-
165
- let description = "";
166
- if (isCurrentlySelectedInSlot) {
167
- description = "Aktuell ausgewählt";
168
- } else if (isCurrentlySelectedInOtherSlot) {
169
- description = "Aktuell in anderem Teaser ausgewählt";
170
- }
171
-
172
- return <div
173
- key={entry.sys.id}
174
- className={classNames({
175
- "border-2 border-red-300": isCurrentlySelectedInSlot,
176
- "border-2 border-green-300": isCurrentlySelectedInOtherSlot,
177
- })}
178
- >
179
- <EntityList.Item
180
- isLoading={loading}
181
- entityType={format(new Date(entry.fields.teaserDate?.de || entry.fields.date?.de), "dd.MM.yyyy HH:mm")}
182
- title={entry.fields.title?.de}
183
- onClick={() => onEntryClick(entry)}
184
- thumbnailUrl={getArticleThumbnailUrl(entry)}
185
- description={description}
186
- status={status}
187
- />
188
- </div>;
189
- })}
190
- </EntityList>
191
- <Pagination
192
- activePage={page}
193
- onPageChange={setPage}
194
- itemsPerPage={limit}
195
- isLastPage={data.total / limit <= page + 1}
196
- />
197
- </div>
198
- );
1
+ import {
2
+ EntityList, Pagination, Spinner, Select, Autocomplete, TextInput,
3
+ } from "@contentful/f36-components";
4
+ import {getContentfulClient} from "../../../lib/contentfulClient";
5
+ import {
6
+ useEffect, useState,
7
+ } from "react";
8
+ import format from "date-fns/format";
9
+ import useDebounce from "../../../hooks/useDebounce";
10
+ import classNames from "classnames";
11
+
12
+ export const NewestArticles = ({
13
+ slotState = {},
14
+ portals = {},
15
+ sdk,
16
+ onEntryClick = () => alert("onEntryClick missing"),
17
+ getArticleThumbnailUrl = (article) => alert("getArticleThumbnailUrl missing"),
18
+ }) => {
19
+ const {
20
+ portal,
21
+ slotId,
22
+ } = sdk.parameters.invocation;
23
+ const contentfulClient = getContentfulClient();
24
+ const limit = 25;
25
+ const [searchQuery, setSearchQuery] = useState("");
26
+ const [authorSearchQuery, setAuthorSearchQuery] = useState("");
27
+ const [enabledPortals, setEnabledPortals] = useState([portal]);
28
+ const [selectedPortal, setSelectedPortal] = useState(portal);
29
+ const [loading, setLoading] = useState(true);
30
+ const [data, setData] = useState({
31
+ items: [],
32
+ total: 0,
33
+ });
34
+ const [page, setPage] = useState(0);
35
+ const [selectedAuthor, setSelectedAuthor] = useState(null);
36
+ const [authors, setAuthors] = useState([]);
37
+ const [authorsLoading, setAuthorsLoading] = useState(false);
38
+
39
+ const debouncedSearch = useDebounce(searchQuery, 500);
40
+ const debouncedAuthorSearch = useDebounce(authorSearchQuery, 500);
41
+
42
+ useEffect(() => {
43
+ sdk.space.getContentType("article").then(articleType => {
44
+ let enabledPortals = articleType.fields.find(v => v.id === "portal")?.validations?.find(v => !!v.in)?.in;
45
+
46
+ if (enabledPortals) {
47
+ setEnabledPortals(enabledPortals);
48
+ }
49
+ });
50
+ }, []);
51
+
52
+ useEffect(() => {
53
+ setPage(0);
54
+ }, [searchQuery, selectedAuthor, selectedPortal]);
55
+
56
+ useEffect(() => {
57
+ setLoading(true);
58
+ const params = {
59
+ limit: limit,
60
+ skip: page * limit,
61
+ content_type: "article",
62
+ order: "-fields.teaserDate",
63
+ "fields.portal": selectedPortal,
64
+ select: [
65
+ "fields.title",
66
+ "fields.authors",
67
+ "fields.teaserDate",
68
+ "fields.image",
69
+ "fields.date",
70
+ "fields.portal",
71
+ "sys.publishedAt",
72
+ "sys.id",
73
+ ].join(","),
74
+ };
75
+
76
+ if (searchQuery) {
77
+ params["fields.title[match]"] = searchQuery;
78
+ }
79
+
80
+ if (selectedAuthor) {
81
+ params["fields.authors.sys.id"] = selectedAuthor;
82
+ }
83
+
84
+ contentfulClient.getEntries(params).then((response) => {
85
+ setLoading(false);
86
+ setData(response);
87
+ });
88
+ }, [debouncedSearch, selectedAuthor, selectedPortal, limit, page]);
89
+
90
+ useEffect(() => {
91
+ setAuthorsLoading(true);
92
+ const params = {
93
+ limit: 100,
94
+ content_type: "author",
95
+ order: "fields.name",
96
+ "fields.portal": portal,
97
+ };
98
+
99
+ if (debouncedAuthorSearch) {
100
+ params["fields.name[match]"] = debouncedAuthorSearch;
101
+ } else {
102
+ setSelectedAuthor(null);
103
+ }
104
+
105
+ contentfulClient.getEntries(params).then((response) => {
106
+ setAuthors(response?.items || []);
107
+ setAuthorsLoading(false);
108
+ });
109
+ }, [debouncedAuthorSearch, portal]);
110
+
111
+ useEffect(() => {
112
+ if (selectedAuthor) {
113
+ if (!authors.find(author => author.sys.id === selectedAuthor)) {
114
+ setSelectedAuthor(null);
115
+ }
116
+ }
117
+ }, [authors, selectedAuthor]);
118
+
119
+ return (
120
+ <div className="flex flex-col space-y-4 p-4">
121
+ <div className="flex space-x-2">
122
+ <div className="grow">
123
+ <TextInput
124
+ width={"full"}
125
+ placeholder={"Suche nach Titel"}
126
+ onChange={(e) => setSearchQuery(e.target.value)}/>
127
+ </div>
128
+ <div className="grow-0">
129
+ <Autocomplete
130
+ isLoading={authorsLoading}
131
+ onSelectItem={item => setSelectedAuthor(item?.sys?.id)}
132
+ items={authors}
133
+ closeAfterSelect={true}
134
+ itemToString={(item) => item.fields?.name?.de}
135
+ renderItem={(item) => `${item.fields?.name?.de || "Unbekannter Name"}`}
136
+ onInputValueChange={setAuthorSearchQuery}
137
+
138
+ placeholder={'Autor'}
139
+ noMatchesMessage={'Kein Autor gefunden'}
140
+ >
141
+ {(options) => {
142
+ return options.map(option => <span key={option.value}>{option.label}</span>);
143
+ }}
144
+ </Autocomplete>
145
+ </div>
146
+ <div className="grow-0">
147
+ <Select value={selectedPortal} onChange={e => setSelectedPortal(e.target.value)}>
148
+ {Object.keys(portals).filter(v => enabledPortals.includes(v)).sort().map(optionValue => <Select.Option
149
+ key={optionValue}
150
+ value={optionValue}>{portals[optionValue]}</Select.Option>)}
151
+ </Select>
152
+ </div>
153
+ </div>
154
+ <EntityList>
155
+ {loading && <Spinner/>}
156
+ {!loading && data.items.map((entry) => {
157
+ const isCurrentlySelectedInOtherSlot = !! Object.keys(slotState || {}).find(slot => slotState[slot] === entry.sys.id && slot !== slotId);
158
+ const isCurrentlySelectedInSlot = !!Object.keys(slotState || {}).find(slot => slotState[slot] === entry.sys.id && slot === slotId);
159
+
160
+ let status = "draft";
161
+ if (entry.sys.publishedAt) {
162
+ status = "published";
163
+ }
164
+
165
+ let description = "";
166
+ if (isCurrentlySelectedInSlot) {
167
+ description = "Aktuell ausgewählt";
168
+ } else if (isCurrentlySelectedInOtherSlot) {
169
+ description = "Aktuell in anderem Teaser ausgewählt";
170
+ }
171
+
172
+ return <div
173
+ key={entry.sys.id}
174
+ className={classNames({
175
+ "border-2 border-red-300": isCurrentlySelectedInSlot,
176
+ "border-2 border-green-300": isCurrentlySelectedInOtherSlot,
177
+ })}
178
+ >
179
+ <EntityList.Item
180
+ isLoading={loading}
181
+ entityType={format(new Date(entry.fields.teaserDate?.de || entry.fields.date?.de), "dd.MM.yyyy HH:mm")}
182
+ title={entry.fields.title?.de}
183
+ onClick={() => onEntryClick(entry)}
184
+ thumbnailUrl={getArticleThumbnailUrl(entry)}
185
+ description={description}
186
+ status={status}
187
+ />
188
+ </div>;
189
+ })}
190
+ </EntityList>
191
+ <Pagination
192
+ activePage={page}
193
+ onPageChange={setPage}
194
+ itemsPerPage={limit}
195
+ isLastPage={data.total / limit <= page + 1}
196
+ />
197
+ </div>
198
+ );
199
199
  };
@@ -1,87 +1,87 @@
1
- import React, {
2
- useEffect, useState,
3
- } from 'react';
4
- import {NewestArticles} from "./Dialog/NewestArticles";
5
- import {LogicEditor} from './Dialog/LogicEditor';
6
- import {
7
- QueryClient, QueryClientProvider,
8
- } from "@tanstack/react-query";
9
-
10
- const queryClient = new QueryClient();
11
-
12
- const Dialog = ({
13
- sdk,
14
- portals,
15
- getArticleThumbnailUrl,
16
- }) => {
17
- const [slotState, setSlotState] = useState({});
18
- const [logicData, setLogicData] = useState({});
19
-
20
- const selectEntry = (entry) => {
21
- sdk.close({
22
- entry,
23
- isArticleResponse: true,
24
- });
25
- };
26
-
27
- const onSave = (data) => {
28
- sdk.close({
29
- data,
30
- isLogicResponse: true,
31
- });
32
- };
33
-
34
- const loadSlotStateForPage = async () => {
35
- try {
36
- const apiRoot = sdk.parameters.instance.apiRoot;
37
- const portal = sdk.parameters.invocation.portal;
38
- const pageId = sdk.parameters.invocation.entryId;
39
- const date = sdk.parameters.invocation.date;
40
- const teasermanagerUrl = `${apiRoot}/api/findStateForPage?project=${portal}&page=${pageId}&date=${date.toISOString()}`;
41
- const response = await fetch(teasermanagerUrl).then(res => res.json());
42
-
43
- if (response?.message) {
44
- console.error(response.message);
45
- }
46
-
47
- return response || {};
48
- } catch (e) {
49
- console.error(e);
50
- }
51
- };
52
-
53
- useEffect(() => {
54
- loadSlotStateForPage().then(({
55
- data = {},
56
- logicData = {},
57
- }) => {
58
- setSlotState(data);
59
- setLogicData(logicData[sdk.parameters.invocation.slotId]);
60
- });
61
- }, [sdk.parameters.invocation]);
62
-
63
- return <div style={{
64
- backgroundColor: "#FFFFFF",
65
- minHeight: "100vh",
66
- }}>
67
- <QueryClientProvider client={queryClient}>
68
- {sdk.parameters.invocation.isLogicEditor ?
69
- <LogicEditor
70
- sdk={sdk}
71
- onSave={onSave}
72
- logicData={logicData}
73
- portals={portals}
74
- /> :
75
- <NewestArticles
76
- sdk={sdk}
77
- onEntryClick={selectEntry}
78
- slotState={slotState}
79
- getArticleThumbnailUrl={getArticleThumbnailUrl}
80
- portals={portals}
81
- />
82
- }
83
- </QueryClientProvider>
84
- </div>;
85
- };
86
-
87
- export default Dialog;
1
+ import React, {
2
+ useEffect, useState,
3
+ } from 'react';
4
+ import {NewestArticles} from "./Dialog/NewestArticles";
5
+ import {LogicEditor} from './Dialog/LogicEditor';
6
+ import {
7
+ QueryClient, QueryClientProvider,
8
+ } from "@tanstack/react-query";
9
+
10
+ const queryClient = new QueryClient();
11
+
12
+ const Dialog = ({
13
+ sdk,
14
+ portals,
15
+ getArticleThumbnailUrl,
16
+ }) => {
17
+ const [slotState, setSlotState] = useState({});
18
+ const [logicData, setLogicData] = useState({});
19
+
20
+ const selectEntry = (entry) => {
21
+ sdk.close({
22
+ entry,
23
+ isArticleResponse: true,
24
+ });
25
+ };
26
+
27
+ const onSave = (data) => {
28
+ sdk.close({
29
+ data,
30
+ isLogicResponse: true,
31
+ });
32
+ };
33
+
34
+ const loadSlotStateForPage = async () => {
35
+ try {
36
+ const apiRoot = sdk.parameters.instance.apiRoot;
37
+ const portal = sdk.parameters.invocation.portal;
38
+ const pageId = sdk.parameters.invocation.entryId;
39
+ const date = sdk.parameters.invocation.date;
40
+ const teasermanagerUrl = `${apiRoot}/api/findStateForPage?project=${portal}&page=${pageId}&date=${date.toISOString()}`;
41
+ const response = await fetch(teasermanagerUrl).then(res => res.json());
42
+
43
+ if (response?.message) {
44
+ console.error(response.message);
45
+ }
46
+
47
+ return response || {};
48
+ } catch (e) {
49
+ console.error(e);
50
+ }
51
+ };
52
+
53
+ useEffect(() => {
54
+ loadSlotStateForPage().then(({
55
+ data = {},
56
+ logicData = {},
57
+ }) => {
58
+ setSlotState(data);
59
+ setLogicData(logicData[sdk.parameters.invocation.slotId]);
60
+ });
61
+ }, [sdk.parameters.invocation]);
62
+
63
+ return <div style={{
64
+ backgroundColor: "#FFFFFF",
65
+ minHeight: "100vh",
66
+ }}>
67
+ <QueryClientProvider client={queryClient}>
68
+ {sdk.parameters.invocation.isLogicEditor ?
69
+ <LogicEditor
70
+ sdk={sdk}
71
+ onSave={onSave}
72
+ logicData={logicData}
73
+ portals={portals}
74
+ /> :
75
+ <NewestArticles
76
+ sdk={sdk}
77
+ onEntryClick={selectEntry}
78
+ slotState={slotState}
79
+ getArticleThumbnailUrl={getArticleThumbnailUrl}
80
+ portals={portals}
81
+ />
82
+ }
83
+ </QueryClientProvider>
84
+ </div>;
85
+ };
86
+
87
+ export default Dialog;