feeds-fun 1.25.1 → 1.26.0
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/package.json +1 -1
- package/src/components/EntryForList.vue +24 -3
- package/src/components/FeedForList.vue +1 -0
- package/src/components/body_list/EntryBody.vue +70 -17
- package/src/components/body_list/EntryCover.vue +75 -0
- package/src/components/body_list/Reference.vue +55 -0
- package/src/components/body_list/References.vue +16 -0
- package/src/components/main/IntegrationsTable.vue +154 -0
- package/src/components/main/ShowMoreButton.vue +17 -0
- package/src/integrations/YouTube.vue +29 -0
- package/src/logic/api.ts +13 -0
- package/src/logic/enums.ts +32 -0
- package/src/logic/iframeSanitizer.ts +267 -0
- package/src/logic/settings.ts +1 -0
- package/src/logic/tests/iframeSanitizer.test.ts +111 -0
- package/src/logic/tests/utils.test.ts +109 -0
- package/src/logic/types.ts +163 -47
- package/src/logic/utils.ts +31 -1
- package/src/main.ts +12 -0
- package/src/stores/entries.ts +9 -1
- package/src/stores/integrations.ts +14 -0
- package/src/values/Icon.vue +18 -0
- package/src/views/FeedsView.vue +10 -0
- package/src/views/MainView.vue +8 -6
- package/src/views/RulesView.vue +8 -2
package/src/logic/types.ts
CHANGED
|
@@ -84,6 +84,18 @@ export class Feed {
|
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
export type RawFeed = {
|
|
88
|
+
id: string;
|
|
89
|
+
title: string | null;
|
|
90
|
+
description: string | null;
|
|
91
|
+
url: string;
|
|
92
|
+
state: string;
|
|
93
|
+
lastError?: string | null;
|
|
94
|
+
loadedAt?: string | null;
|
|
95
|
+
linkedAt: string;
|
|
96
|
+
collectionIds: string[];
|
|
97
|
+
};
|
|
98
|
+
|
|
87
99
|
export function feedFromJSON({
|
|
88
100
|
id,
|
|
89
101
|
title,
|
|
@@ -94,31 +106,109 @@ export function feedFromJSON({
|
|
|
94
106
|
loadedAt,
|
|
95
107
|
linkedAt,
|
|
96
108
|
collectionIds
|
|
97
|
-
}: {
|
|
98
|
-
id: string;
|
|
99
|
-
title: string;
|
|
100
|
-
description: string;
|
|
101
|
-
url: string;
|
|
102
|
-
state: string;
|
|
103
|
-
lastError: string | null;
|
|
104
|
-
loadedAt: string;
|
|
105
|
-
linkedAt: string;
|
|
106
|
-
collectionIds: string[];
|
|
107
|
-
}): Feed {
|
|
109
|
+
}: RawFeed): Feed {
|
|
108
110
|
return {
|
|
109
111
|
id: toFeedId(id),
|
|
110
112
|
title: title !== null ? title : null,
|
|
111
113
|
description: description !== null ? description : null,
|
|
112
114
|
url: toURL(url),
|
|
113
115
|
state: state,
|
|
114
|
-
lastError: lastError,
|
|
115
|
-
loadedAt: loadedAt !== null ? new Date(loadedAt) : null,
|
|
116
|
+
lastError: lastError !== undefined ? lastError : null,
|
|
117
|
+
loadedAt: loadedAt !== undefined && loadedAt !== null ? new Date(loadedAt) : null,
|
|
116
118
|
linkedAt: new Date(linkedAt),
|
|
117
119
|
isOk: state === "loaded",
|
|
118
120
|
collectionIds: collectionIds.map(toCollectionId)
|
|
119
121
|
};
|
|
120
122
|
}
|
|
121
123
|
|
|
124
|
+
export type ReferenceExtraValue = number | string | null;
|
|
125
|
+
|
|
126
|
+
export type RawReference = {
|
|
127
|
+
kind: e.ReferenceKind;
|
|
128
|
+
url: string;
|
|
129
|
+
title?: string | null;
|
|
130
|
+
mime_type?: string | null;
|
|
131
|
+
width?: number | null;
|
|
132
|
+
height?: number | null;
|
|
133
|
+
duration?: string | null;
|
|
134
|
+
size?: number | null;
|
|
135
|
+
extra?: {[key: string]: ReferenceExtraValue} | null;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
export class Reference {
|
|
139
|
+
readonly kind: e.ReferenceKind;
|
|
140
|
+
readonly url: URL;
|
|
141
|
+
readonly title: string | null;
|
|
142
|
+
readonly mimeType: string | null;
|
|
143
|
+
readonly width: number | null;
|
|
144
|
+
readonly height: number | null;
|
|
145
|
+
readonly duration: string | null;
|
|
146
|
+
readonly size: number | null;
|
|
147
|
+
readonly extra: {[key: string]: ReferenceExtraValue} | null;
|
|
148
|
+
|
|
149
|
+
constructor({
|
|
150
|
+
kind,
|
|
151
|
+
url,
|
|
152
|
+
title,
|
|
153
|
+
mimeType,
|
|
154
|
+
width,
|
|
155
|
+
height,
|
|
156
|
+
duration,
|
|
157
|
+
size,
|
|
158
|
+
extra
|
|
159
|
+
}: {
|
|
160
|
+
kind: e.ReferenceKind;
|
|
161
|
+
url: URL;
|
|
162
|
+
title: string | null;
|
|
163
|
+
mimeType: string | null;
|
|
164
|
+
width: number | null;
|
|
165
|
+
height: number | null;
|
|
166
|
+
duration: string | null;
|
|
167
|
+
size: number | null;
|
|
168
|
+
extra: {[key: string]: ReferenceExtraValue} | null;
|
|
169
|
+
}) {
|
|
170
|
+
this.kind = kind;
|
|
171
|
+
this.url = url;
|
|
172
|
+
this.title = title;
|
|
173
|
+
this.mimeType = mimeType;
|
|
174
|
+
this.width = width;
|
|
175
|
+
this.height = height;
|
|
176
|
+
this.duration = duration;
|
|
177
|
+
this.size = size;
|
|
178
|
+
this.extra = extra;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
youtubeId(): string | null {
|
|
182
|
+
const youtubeId = this.extra?.youtube_id;
|
|
183
|
+
|
|
184
|
+
return typeof youtubeId === "string" && youtubeId.length > 0 ? youtubeId : null;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function referenceFromJSON({
|
|
189
|
+
kind,
|
|
190
|
+
url,
|
|
191
|
+
title,
|
|
192
|
+
mime_type,
|
|
193
|
+
width,
|
|
194
|
+
height,
|
|
195
|
+
duration,
|
|
196
|
+
size,
|
|
197
|
+
extra
|
|
198
|
+
}: RawReference): Reference {
|
|
199
|
+
return new Reference({
|
|
200
|
+
kind: kind,
|
|
201
|
+
url: toURL(url),
|
|
202
|
+
title: title !== undefined ? title : null,
|
|
203
|
+
mimeType: mime_type !== undefined ? mime_type : null,
|
|
204
|
+
width: width !== undefined ? width : null,
|
|
205
|
+
height: height !== undefined ? height : null,
|
|
206
|
+
duration: duration !== undefined ? duration : null,
|
|
207
|
+
size: size !== undefined ? size : null,
|
|
208
|
+
extra: extra !== undefined ? extra : null
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
122
212
|
export class Entry {
|
|
123
213
|
readonly id: EntryId;
|
|
124
214
|
readonly feedId: FeedId;
|
|
@@ -131,6 +221,7 @@ export class Entry {
|
|
|
131
221
|
readonly scoreToZero: number;
|
|
132
222
|
readonly publishedAt: Date;
|
|
133
223
|
body: string | null;
|
|
224
|
+
references: Reference[] | null;
|
|
134
225
|
|
|
135
226
|
constructor({
|
|
136
227
|
id,
|
|
@@ -142,7 +233,8 @@ export class Entry {
|
|
|
142
233
|
score,
|
|
143
234
|
scoreContributions,
|
|
144
235
|
publishedAt,
|
|
145
|
-
body
|
|
236
|
+
body,
|
|
237
|
+
references
|
|
146
238
|
}: {
|
|
147
239
|
id: EntryId;
|
|
148
240
|
feedId: FeedId;
|
|
@@ -154,6 +246,7 @@ export class Entry {
|
|
|
154
246
|
scoreContributions: {[key: string]: number};
|
|
155
247
|
publishedAt: Date;
|
|
156
248
|
body: string | null;
|
|
249
|
+
references: Reference[] | null;
|
|
157
250
|
}) {
|
|
158
251
|
this.id = id;
|
|
159
252
|
this.feedId = feedId;
|
|
@@ -165,6 +258,7 @@ export class Entry {
|
|
|
165
258
|
this.scoreContributions = scoreContributions;
|
|
166
259
|
this.publishedAt = publishedAt;
|
|
167
260
|
this.body = body;
|
|
261
|
+
this.references = references;
|
|
168
262
|
|
|
169
263
|
this.scoreToZero = -Math.abs(score);
|
|
170
264
|
}
|
|
@@ -190,21 +284,21 @@ export class Entry {
|
|
|
190
284
|
}
|
|
191
285
|
}
|
|
192
286
|
|
|
193
|
-
export
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
): Entry {
|
|
287
|
+
export type RawEntry = {
|
|
288
|
+
id: string;
|
|
289
|
+
feedId: string;
|
|
290
|
+
title: string;
|
|
291
|
+
url: string;
|
|
292
|
+
tags: number[];
|
|
293
|
+
markers: string[];
|
|
294
|
+
score: number;
|
|
295
|
+
scoreContributions: {[key: number]: number};
|
|
296
|
+
publishedAt: string;
|
|
297
|
+
body?: string | null;
|
|
298
|
+
references?: RawReference[] | null;
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
export function entryFromJSON(rawEntry: RawEntry, tagsMapping: {[key: number]: string}): Entry {
|
|
208
302
|
const contributions: {[key: string]: number} = {};
|
|
209
303
|
|
|
210
304
|
for (const key in rawEntry.scoreContributions) {
|
|
@@ -228,8 +322,11 @@ export function entryFromJSON(
|
|
|
228
322
|
// map keys from int to string
|
|
229
323
|
scoreContributions: contributions,
|
|
230
324
|
publishedAt: new Date(rawEntry.publishedAt),
|
|
231
|
-
|
|
232
|
-
|
|
325
|
+
body: rawEntry.body !== undefined ? rawEntry.body : null,
|
|
326
|
+
references:
|
|
327
|
+
rawEntry.references !== undefined && rawEntry.references !== null
|
|
328
|
+
? rawEntry.references.map(referenceFromJSON)
|
|
329
|
+
: null
|
|
233
330
|
});
|
|
234
331
|
}
|
|
235
332
|
|
|
@@ -279,17 +376,14 @@ export type EntryInfo = {
|
|
|
279
376
|
readonly publishedAt: Date;
|
|
280
377
|
};
|
|
281
378
|
|
|
282
|
-
export
|
|
283
|
-
title,
|
|
284
|
-
body,
|
|
285
|
-
url,
|
|
286
|
-
publishedAt
|
|
287
|
-
}: {
|
|
379
|
+
export type RawEntryInfo = {
|
|
288
380
|
title: string;
|
|
289
381
|
body: string;
|
|
290
382
|
url: string;
|
|
291
383
|
publishedAt: string;
|
|
292
|
-
}
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
export function entryInfoFromJSON({title, body, url, publishedAt}: RawEntryInfo): EntryInfo {
|
|
293
387
|
return {title, body, url: toURL(url), publishedAt: new Date(publishedAt)};
|
|
294
388
|
}
|
|
295
389
|
|
|
@@ -301,19 +395,15 @@ export type FeedInfo = {
|
|
|
301
395
|
readonly isLinked: boolean;
|
|
302
396
|
};
|
|
303
397
|
|
|
304
|
-
export
|
|
305
|
-
url,
|
|
306
|
-
title,
|
|
307
|
-
description,
|
|
308
|
-
entries,
|
|
309
|
-
isLinked
|
|
310
|
-
}: {
|
|
398
|
+
export type RawFeedInfo = {
|
|
311
399
|
url: string;
|
|
312
400
|
title: string;
|
|
313
401
|
description: string;
|
|
314
|
-
entries:
|
|
402
|
+
entries: RawEntryInfo[];
|
|
315
403
|
isLinked: boolean;
|
|
316
|
-
}
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
export function feedInfoFromJSON({url, title, description, entries, isLinked}: RawFeedInfo): FeedInfo {
|
|
317
407
|
return {
|
|
318
408
|
url: toURL(url),
|
|
319
409
|
title,
|
|
@@ -510,6 +600,32 @@ export function collectionFeedInfoFromJSON({
|
|
|
510
600
|
});
|
|
511
601
|
}
|
|
512
602
|
|
|
603
|
+
export class IntegrationInfo {
|
|
604
|
+
readonly name: string;
|
|
605
|
+
readonly discovery: boolean;
|
|
606
|
+
readonly postprocessing: boolean;
|
|
607
|
+
|
|
608
|
+
constructor({name, discovery, postprocessing}: {name: string; discovery: boolean; postprocessing: boolean}) {
|
|
609
|
+
this.name = name;
|
|
610
|
+
this.discovery = discovery;
|
|
611
|
+
this.postprocessing = postprocessing;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
export type RawIntegrationInfo = {
|
|
616
|
+
name: string;
|
|
617
|
+
discovery: boolean;
|
|
618
|
+
postprocessing: boolean;
|
|
619
|
+
};
|
|
620
|
+
|
|
621
|
+
export function integrationInfoFromJSON({name, discovery, postprocessing}: RawIntegrationInfo): IntegrationInfo {
|
|
622
|
+
return new IntegrationInfo({
|
|
623
|
+
name: name,
|
|
624
|
+
discovery: discovery,
|
|
625
|
+
postprocessing: postprocessing
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
|
|
513
629
|
export class ApiMessage {
|
|
514
630
|
readonly type: string;
|
|
515
631
|
readonly code: string;
|
package/src/logic/utils.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import _ from "lodash";
|
|
2
2
|
import type * as t from "@/logic/types";
|
|
3
3
|
import DOMPurify from "dompurify";
|
|
4
|
+
import * as iframeSanitizer from "@/logic/iframeSanitizer";
|
|
4
5
|
|
|
5
6
|
const REQUIRED_LINK_ATTRIBUTES = {
|
|
6
7
|
target: "_blank",
|
|
@@ -8,6 +9,33 @@ const REQUIRED_LINK_ATTRIBUTES = {
|
|
|
8
9
|
referrerpolicy: "strict-origin-when-cross-origin"
|
|
9
10
|
} as const;
|
|
10
11
|
|
|
12
|
+
const INTERFERING_BODY_ATTRIBUTES = new Set(["class", "id", "style"]);
|
|
13
|
+
|
|
14
|
+
const INTERFERING_BODY_ATTRIBUTE_PREFIXES = ["data-"];
|
|
15
|
+
|
|
16
|
+
function isInterferingBodyAttribute(attributeName: string) {
|
|
17
|
+
if (INTERFERING_BODY_ATTRIBUTES.has(attributeName)) {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return INTERFERING_BODY_ATTRIBUTE_PREFIXES.some((prefix) => attributeName.startsWith(prefix));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function removeInterferingAttributes(html: string) {
|
|
25
|
+
const parsed = new DOMParser().parseFromString(html, "text/html");
|
|
26
|
+
const elements = parsed.body.querySelectorAll("*");
|
|
27
|
+
|
|
28
|
+
for (const element of elements) {
|
|
29
|
+
for (const attribute of Array.from(element.attributes)) {
|
|
30
|
+
if (isInterferingBodyAttribute(attribute.name)) {
|
|
31
|
+
element.removeAttribute(attribute.name);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return parsed.body.innerHTML;
|
|
37
|
+
}
|
|
38
|
+
|
|
11
39
|
function hardenLinksSecurityAttributes(html: string) {
|
|
12
40
|
const parsed = new DOMParser().parseFromString(html, "text/html");
|
|
13
41
|
const links = parsed.body.querySelectorAll("[href]");
|
|
@@ -109,12 +137,14 @@ export function purifyBody({raw, default_}: {raw: string | null; default_: strin
|
|
|
109
137
|
return default_;
|
|
110
138
|
}
|
|
111
139
|
|
|
112
|
-
let body = DOMPurify.sanitize(raw).trim();
|
|
140
|
+
let body = DOMPurify.sanitize(raw, iframeSanitizer.DOM_PURIFY_IFRAME_OPTIONS).trim();
|
|
113
141
|
|
|
114
142
|
if (body.length === 0) {
|
|
115
143
|
return default_;
|
|
116
144
|
}
|
|
117
145
|
|
|
146
|
+
body = removeInterferingAttributes(body);
|
|
147
|
+
body = iframeSanitizer.sanitizeIframes(body);
|
|
118
148
|
body = hardenLinksSecurityAttributes(body);
|
|
119
149
|
|
|
120
150
|
return body;
|
package/src/main.ts
CHANGED
|
@@ -65,12 +65,18 @@ import SocialLink from "./values/SocialLink.vue";
|
|
|
65
65
|
import BodyListReverseTimeColumn from "./components/body_list/ReverseTimeColumn.vue";
|
|
66
66
|
import BodyListFaviconColumn from "./components/body_list/FaviconColumn.vue";
|
|
67
67
|
import BodyListEntryBody from "./components/body_list/EntryBody.vue";
|
|
68
|
+
import BodyListEntryCover from "./components/body_list/EntryCover.vue";
|
|
69
|
+
import BodyListReferences from "./components/body_list/References.vue";
|
|
70
|
+
import BodyListReference from "./components/body_list/Reference.vue";
|
|
71
|
+
import IntegrationsYouTube from "./integrations/YouTube.vue";
|
|
68
72
|
|
|
69
73
|
import MainDescription from "./components/main/Description.vue";
|
|
70
74
|
import MainItem from "./components/main/Item.vue";
|
|
71
75
|
import MainNewsTitle from "./components/main/NewsTitle.vue";
|
|
72
76
|
import MainHeaderLine from "./components/main/HeaderLine.vue";
|
|
73
77
|
import MainBlock from "./components/main/Block.vue";
|
|
78
|
+
import MainIntegrationsTable from "./components/main/IntegrationsTable.vue";
|
|
79
|
+
import MainShowMoreButton from "./components/main/ShowMoreButton.vue";
|
|
74
80
|
|
|
75
81
|
import SidePanelCollapseButton from "./components/side_pannel/CollapseButton.vue";
|
|
76
82
|
|
|
@@ -138,12 +144,18 @@ app.component("SocialLink", SocialLink);
|
|
|
138
144
|
app.component("BodyListReverseTimeColumn", BodyListReverseTimeColumn);
|
|
139
145
|
app.component("BodyListFaviconColumn", BodyListFaviconColumn);
|
|
140
146
|
app.component("BodyListEntryBody", BodyListEntryBody);
|
|
147
|
+
app.component("BodyListEntryCover", BodyListEntryCover);
|
|
148
|
+
app.component("BodyListReferences", BodyListReferences);
|
|
149
|
+
app.component("BodyListReference", BodyListReference);
|
|
150
|
+
app.component("IntegrationsYouTube", IntegrationsYouTube);
|
|
141
151
|
|
|
142
152
|
app.component("MainDescription", MainDescription);
|
|
143
153
|
app.component("MainItem", MainItem);
|
|
144
154
|
app.component("MainNewsTitle", MainNewsTitle);
|
|
145
155
|
app.component("MainHeaderLine", MainHeaderLine);
|
|
146
156
|
app.component("MainBlock", MainBlock);
|
|
157
|
+
app.component("MainIntegrationsTable", MainIntegrationsTable);
|
|
158
|
+
app.component("MainShowMoreButton", MainShowMoreButton);
|
|
147
159
|
|
|
148
160
|
app.component("SidePanelCollapseButton", SidePanelCollapseButton);
|
|
149
161
|
|
package/src/stores/entries.ts
CHANGED
|
@@ -96,6 +96,10 @@ export const useEntriesStore = defineStore("entriesStore", () => {
|
|
|
96
96
|
entry.body = existingEntry.body;
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
if (entry.references === null && existingEntry.references !== null) {
|
|
100
|
+
entry.references = existingEntry.references;
|
|
101
|
+
}
|
|
102
|
+
|
|
99
103
|
if (!updateTags) {
|
|
100
104
|
entry.tags = _.cloneDeep(existingEntry.tags);
|
|
101
105
|
}
|
|
@@ -202,7 +206,11 @@ export const useEntriesStore = defineStore("entriesStore", () => {
|
|
|
202
206
|
});
|
|
203
207
|
|
|
204
208
|
function requestFullEntry({entryId}: {entryId: t.EntryId}) {
|
|
205
|
-
if (
|
|
209
|
+
if (
|
|
210
|
+
entryId in entries.value &&
|
|
211
|
+
entries.value[entryId].body !== null &&
|
|
212
|
+
entries.value[entryId].references !== null
|
|
213
|
+
) {
|
|
206
214
|
return;
|
|
207
215
|
}
|
|
208
216
|
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {defineStore} from "pinia";
|
|
2
|
+
import {computedAsync} from "@vueuse/core";
|
|
3
|
+
|
|
4
|
+
import * as api from "@/logic/api";
|
|
5
|
+
|
|
6
|
+
export const useIntegrationsStore = defineStore("integrationsStore", () => {
|
|
7
|
+
const integrations = computedAsync(async () => {
|
|
8
|
+
return await api.getIntegrations();
|
|
9
|
+
}, []);
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
integrations
|
|
13
|
+
};
|
|
14
|
+
});
|
package/src/values/Icon.vue
CHANGED
|
@@ -20,6 +20,15 @@
|
|
|
20
20
|
IconChevronsRight,
|
|
21
21
|
IconLayoutSidebarLeftCollapse,
|
|
22
22
|
IconLayoutSidebarLeftExpand,
|
|
23
|
+
IconUser,
|
|
24
|
+
IconMessageCircle,
|
|
25
|
+
IconWorld,
|
|
26
|
+
IconPlayerPlay,
|
|
27
|
+
IconVolume,
|
|
28
|
+
IconPhoto,
|
|
29
|
+
IconFileText,
|
|
30
|
+
IconInfoSquareFilled,
|
|
31
|
+
IconCheck,
|
|
23
32
|
IconX,
|
|
24
33
|
IconMoodSmile,
|
|
25
34
|
IconMoodSad
|
|
@@ -38,6 +47,15 @@
|
|
|
38
47
|
"chevrons-left": IconChevronsLeft,
|
|
39
48
|
"sidebar-left-collapse": IconLayoutSidebarLeftCollapse,
|
|
40
49
|
"sidebar-left-expand": IconLayoutSidebarLeftExpand,
|
|
50
|
+
user: IconUser,
|
|
51
|
+
comments: IconMessageCircle,
|
|
52
|
+
world: IconWorld,
|
|
53
|
+
"player-play": IconPlayerPlay,
|
|
54
|
+
volume: IconVolume,
|
|
55
|
+
photo: IconPhoto,
|
|
56
|
+
"file-text": IconFileText,
|
|
57
|
+
"info-square-filled": IconInfoSquareFilled,
|
|
58
|
+
check: IconCheck,
|
|
41
59
|
x: IconX,
|
|
42
60
|
"face-smile": IconMoodSmile,
|
|
43
61
|
"face-sad": IconMoodSad
|
package/src/views/FeedsView.vue
CHANGED
|
@@ -60,12 +60,14 @@
|
|
|
60
60
|
import {computed, ref, onUnmounted, watch, provide} from "vue";
|
|
61
61
|
import {computedAsync} from "@vueuse/core";
|
|
62
62
|
import {useGlobalSettingsStore} from "@/stores/globalSettings";
|
|
63
|
+
import {useGlobalState} from "@/stores/globalState";
|
|
63
64
|
import {useFeedsStore} from "@/stores/feeds";
|
|
64
65
|
import * as api from "@/logic/api";
|
|
65
66
|
import type * as t from "@/logic/types";
|
|
66
67
|
import * as e from "@/logic/enums";
|
|
67
68
|
|
|
68
69
|
const globalSettings = useGlobalSettingsStore();
|
|
70
|
+
const globalState = useGlobalState();
|
|
69
71
|
|
|
70
72
|
const feedsStore = useFeedsStore();
|
|
71
73
|
|
|
@@ -73,7 +75,15 @@
|
|
|
73
75
|
|
|
74
76
|
globalSettings.mainPanelMode = e.MainPanelMode.Feeds;
|
|
75
77
|
|
|
78
|
+
const readyToUseSettings = computed(() => {
|
|
79
|
+
return globalSettings.userSettingsPresent || !globalState.loginConfirmed;
|
|
80
|
+
});
|
|
81
|
+
|
|
76
82
|
const sortedFeeds = computed(() => {
|
|
83
|
+
if (!readyToUseSettings.value) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
77
87
|
let sorted = Object.values(feedsStore.feeds);
|
|
78
88
|
|
|
79
89
|
if (sorted.length === 0) {
|
package/src/views/MainView.vue
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
<main-block>
|
|
12
12
|
<h1 class="m-0 text-5xl">Feeds Fun</h1>
|
|
13
|
-
<p class="mt-2 text-2xl">
|
|
13
|
+
<p class="mt-2 text-2xl">Personalized News You Control</p>
|
|
14
14
|
|
|
15
15
|
<div class="h-12 grid grid-flow-col auto-cols-fr gap-3 w-max mx-auto">
|
|
16
16
|
<a
|
|
@@ -224,14 +224,16 @@
|
|
|
224
224
|
<div
|
|
225
225
|
v-if="showMoreCollectionsButtonRequired"
|
|
226
226
|
class="mt-4 text-center">
|
|
227
|
-
<button
|
|
228
|
-
class="ffun-main-button short"
|
|
229
|
-
@click="showAllCollections = !showAllCollections">
|
|
230
|
-
{{ showAllCollections ? "Show less" : "Show more" }}
|
|
231
|
-
</button>
|
|
227
|
+
<main-show-more-button v-model:expanded="showAllCollections" />
|
|
232
228
|
</div>
|
|
233
229
|
</main-block>
|
|
234
230
|
|
|
231
|
+
<main-header-line v-if="settings.hasIntegrations"> Advanced support for popular sources </main-header-line>
|
|
232
|
+
|
|
233
|
+
<main-block v-if="settings.hasIntegrations">
|
|
234
|
+
<main-integrations-table />
|
|
235
|
+
</main-block>
|
|
236
|
+
|
|
235
237
|
<main-header-line> Here, take a peek </main-header-line>
|
|
236
238
|
<div class="text-center p-5">
|
|
237
239
|
<img
|
package/src/views/RulesView.vue
CHANGED
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
import {useRoute, useRouter} from "vue-router";
|
|
42
42
|
import {computedAsync} from "@vueuse/core";
|
|
43
43
|
import {useGlobalSettingsStore} from "@/stores/globalSettings";
|
|
44
|
+
import {useGlobalState} from "@/stores/globalState";
|
|
44
45
|
import _ from "lodash";
|
|
45
46
|
import * as utils from "@/logic/utils";
|
|
46
47
|
import * as api from "@/logic/api";
|
|
@@ -57,6 +58,7 @@
|
|
|
57
58
|
provide("eventsViewName", "rules");
|
|
58
59
|
|
|
59
60
|
const globalSettings = useGlobalSettingsStore();
|
|
61
|
+
const globalState = useGlobalState();
|
|
60
62
|
|
|
61
63
|
tagsFilterState.setSyncingTagsWithRoute({
|
|
62
64
|
tagsStates: tagsStates.value as unknown as tagsFilterState.Storage,
|
|
@@ -75,7 +77,11 @@
|
|
|
75
77
|
router.push({name: e.MainPanelMode.Entries, params: {}});
|
|
76
78
|
}
|
|
77
79
|
|
|
78
|
-
const
|
|
80
|
+
const readyToUseSettings = computed(() => {
|
|
81
|
+
return globalSettings.userSettingsPresent || !globalState.loginConfirmed;
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const loading = computed(() => rules.value === null || !readyToUseSettings.value);
|
|
79
85
|
|
|
80
86
|
const rules = computedAsync(async () => {
|
|
81
87
|
// force refresh
|
|
@@ -84,7 +90,7 @@
|
|
|
84
90
|
}, null);
|
|
85
91
|
|
|
86
92
|
const sortedRules = computed(() => {
|
|
87
|
-
if (!rules.value) {
|
|
93
|
+
if (!rules.value || !readyToUseSettings.value) {
|
|
88
94
|
return null;
|
|
89
95
|
}
|
|
90
96
|
|