Dark Mode
diff --git a/src/features/settings/components/settings.css b/src/features/settings/components/settings.css
index 0ac288df..d2cb93af 100644
--- a/src/features/settings/components/settings.css
+++ b/src/features/settings/components/settings.css
@@ -46,6 +46,53 @@ Modal
.settingRow:not(:last-child) {
border-bottom: 1px solid var(--card-content-divider);
}
+
+.settingContent .form {
+ display: flex;
+ flex-direction: row;
+ gap: 12px;
+}
+
+.settingContent input[type=text] {
+ flex:1;
+ background-color: var(--settings-input-background-color);
+ border:1px solid var(--settings-input-border-color);
+ border-radius: 50px;
+ padding:6px 18px;
+ color:var(--settings-input-text-color);
+ font-size: 14px;
+}
+
+.settingContent input[type=text]::placeholder { /* Chrome, Firefox, Opera, Safari 10.1+ */
+ color: var(--settings-input-placeholder-color);
+ opacity: 1;
+ font-size: 14px;
+}
+
+.settingContent input[type=text]:focus {
+ border-color:var(--settings-input-border-focus-color)
+}
+
+.settingContent button {
+ display: flex;
+ align-items: center;
+ column-gap: 8px;
+ background-color:none;
+ border:none;
+ border-radius: 50px;
+ padding:6px 12px;
+ font-size: 14px;
+ font-weight: bold;
+ cursor: pointer;
+}
+
+.rssButton {
+ background-color: #EE802F;
+ color:white;
+}
+.rssButton:hover {
+ opacity: 0.9;
+}
.settingContent {
width: 100%;
flex: 1;
@@ -129,6 +176,24 @@ Select styles
border-radius: 20px !important;
color: var(--tag-secondary-color) !important;
}
+.rss-sources .hackertab__indicators {
+ display: none;
+}
+
+.rss-widget
+.rssUrlControl {
+ margin: 6px 0;
+ color: var(--primary-text-color);
+}
+
+.rssUrlInput {
+ width: 66%;
+ margin-right: 4px;
+ padding: 4px;
+ background-color: var(--card-background-color) !important;
+ border-color: var(--tag-border-color) !important;
+ color: var(--primary-text-color)
+}
@media (max-width: 768px) {
.Modal {
diff --git a/src/lib/analytics.ts b/src/lib/analytics.ts
index d332a122..03148ed4 100644
--- a/src/lib/analytics.ts
+++ b/src/lib/analytics.ts
@@ -19,6 +19,7 @@ enum Objects {
CHANGE_LOG = 'Change Log',
MARKETING_CAMPAIGN = 'Marketing Campaign',
ONBOARDING = 'Onboarding',
+ RSS = 'Rss',
}
enum Verbs {
@@ -274,6 +275,14 @@ export const trackOnboardingFinish = () => {
})
}
+export const trackRssSourceAdd = (source: string) => {
+ trackEvent({
+ object: Objects.RSS,
+ verb: Verbs.ADD,
+ attributes: { [Attributes.SOURCE]: source },
+ })
+}
+
// Identification
export const identifyUserLanguages = (languages: string[]) => {
@@ -301,6 +310,7 @@ export const identifyUserOccupation = (occupation: string) => {
identifyUserProperty(Attributes.OCCUPATION, occupation)
}
+
// Private functions
type trackEventProps = {
object: Exclude
diff --git a/src/stores/bookmarks.ts b/src/stores/bookmarks.ts
index bd3b681d..d99827b0 100644
--- a/src/stores/bookmarks.ts
+++ b/src/stores/bookmarks.ts
@@ -1,6 +1,7 @@
-import create from 'zustand';
-import { persist } from 'zustand/middleware'
-import { BookmarkedPost } from "src/features/bookmarks"
+import { create } from 'zustand';
+
+import { BookmarkedPost } from "src/features/bookmarks";
+import { persist } from 'zustand/middleware';
type BookmarksState = {
userBookmarks: BookmarkedPost[]
diff --git a/src/stores/preferences.ts b/src/stores/preferences.ts
index d988c254..515d7631 100644
--- a/src/stores/preferences.ts
+++ b/src/stores/preferences.ts
@@ -1,9 +1,9 @@
import { Occupation } from 'src/features/onboarding/types'
import { Tag } from 'src/features/remoteConfig'
import { enhanceTags } from 'src/utils/DataEnhancement'
-import create from 'zustand'
+import { create } from 'zustand'
import { persist } from 'zustand/middleware'
-import { CardSettingsType, ListingMode, SelectedCard, Theme } from '../types'
+import { CardSettingsType, ListingMode, SelectedCard, SupportedCardType, Theme } from '../types'
export type UserPreferencesState = {
userSelectedTags: Tag[]
@@ -16,6 +16,7 @@ export type UserPreferencesState = {
cards: SelectedCard[]
cardsSettings: Record
firstSeenDate: number
+ userCustomCards: SupportedCardType[]
}
type UserPreferencesStoreActions = {
@@ -28,6 +29,7 @@ type UserPreferencesStoreActions = {
setTags: (selectedTags: Tag[]) => void
setCardSettings: (card: string, settings: CardSettingsType) => void
markOnboardingAsCompleted: (occupation: Omit | null) => void
+ setUserCustomCards: (cards: SupportedCardType[]) => void
}
export const useUserPreferences = create(
@@ -43,11 +45,12 @@ export const useUserPreferences = create(
openLinksNewTab: true,
firstSeenDate: Date.now(),
cards: [
- { id: 0, name: 'github' },
- { id: 1, name: 'hackernews' },
- { id: 2, name: 'devto' },
- { id: 3, name: 'producthunt' },
+ { id: 0, name: 'github', type: 'supported' },
+ { id: 1, name: 'hackernews', type: 'supported' },
+ { id: 2, name: 'devto', type: 'supported' },
+ { id: 3, name: 'producthunt', type: 'supported' },
],
+ userCustomCards: [],
setSearchEngine: (searchEngine: string) => set({ searchEngine: searchEngine }),
setListingMode: (listingMode: ListingMode) => set({ listingMode: listingMode }),
setTheme: (theme: Theme) => set({ theme: theme }),
@@ -70,6 +73,7 @@ export const useUserPreferences = create(
onboardingCompleted: true,
onboardingResult: occupation,
})),
+ setUserCustomCards: (cards: SupportedCardType[]) => set({ userCustomCards: cards }),
}),
{
name: 'preferences_storage',
diff --git a/src/types/index.ts b/src/types/index.ts
index 69eb3ca4..715f6937 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -1,5 +1,4 @@
import { Tag } from 'src/features/remoteConfig'
-import { SupportedCard } from 'src/config'
export type SearchEngineType = {
url: string
@@ -9,6 +8,7 @@ export type SearchEngineType = {
export type SelectedCard = {
id: number
name: string
+ type: 'rss' | 'supported'
}
export type SelectedTag = {
@@ -75,8 +75,19 @@ export type Conference = BaseEntry & {
country?: string
}
+export type SupportedCardType = {
+ value: string
+ analyticsTag: string
+ label: string
+ link: string
+ type: 'rss' | 'supported'
+ component?: React.FunctionComponent
+ feedUrl?: string
+ icon?: React.ReactNode | string
+}
+
export type CardPropsType = {
- meta: SupportedCard
+ meta: Omit
withAds: boolean
}
diff --git a/src/utils/UrlUtils.ts b/src/utils/UrlUtils.ts
index 6df0a2a6..c134bd2b 100644
--- a/src/utils/UrlUtils.ts
+++ b/src/utils/UrlUtils.ts
@@ -4,4 +4,17 @@ export const addHttpsProtocol = (url: string): string => {
url = 'https:' + url
}
return url
-}
\ No newline at end of file
+}
+
+export const isValidURL = (str: string): boolean => {
+ var pattern = new RegExp(
+ '^(https?:\\/\\/)?' + // protocol
+ '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,})' + // domain name
+ '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path
+ '(\\?[;&a-z\\d%_.~+=-]*)?' + // query string
+ '(\\#[-a-z\\d_]*)?$',
+ 'i'
+ ) // fragment locator
+ return !!pattern.test(str)
+}
+
diff --git a/yarn.lock b/yarn.lock
index e0f8fbd7..66de8861 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3254,6 +3254,15 @@ axios@^0.21.2:
dependencies:
follow-redirects "^1.14.0"
+axios@^1.2.6:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/axios/-/axios-1.3.1.tgz#80bf6c8dbb46e6db1fa8fe9ab114c1ca7405c2ee"
+ integrity sha512-78pWJsQTceInlyaeBQeYZ/QgZeWS8hGeKiIJiDKQe3hEyBb7sEMq0K4gjx+Va6WHTYO4zI/RRl8qGRzn0YMadA==
+ dependencies:
+ follow-redirects "^1.15.0"
+ form-data "^4.0.0"
+ proxy-from-env "^1.1.0"
+
axobject-query@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be"
@@ -5041,6 +5050,15 @@ dom-serializer@^1.0.1:
domhandler "^4.2.0"
entities "^2.0.0"
+dom-serializer@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53"
+ integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==
+ dependencies:
+ domelementtype "^2.3.0"
+ domhandler "^5.0.2"
+ entities "^4.2.0"
+
domain-browser@^1.1.1:
version "1.2.0"
resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
@@ -5051,7 +5069,7 @@ domelementtype@1:
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f"
integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==
-domelementtype@^2.0.1, domelementtype@^2.2.0:
+domelementtype@^2.0.1, domelementtype@^2.2.0, domelementtype@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d"
integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==
@@ -5070,6 +5088,13 @@ domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1:
dependencies:
domelementtype "^2.2.0"
+domhandler@^5.0.1, domhandler@^5.0.2:
+ version "5.0.3"
+ resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31"
+ integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==
+ dependencies:
+ domelementtype "^2.3.0"
+
dompurify@^2.2.7:
version "2.4.0"
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.4.0.tgz#c9c88390f024c2823332615c9e20a453cf3825dd"
@@ -5092,6 +5117,15 @@ domutils@^2.5.2, domutils@^2.8.0:
domelementtype "^2.2.0"
domhandler "^4.2.0"
+domutils@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.0.1.tgz#696b3875238338cb186b6c0612bd4901c89a4f1c"
+ integrity sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==
+ dependencies:
+ dom-serializer "^2.0.0"
+ domelementtype "^2.3.0"
+ domhandler "^5.0.1"
+
dot-case@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751"
@@ -5230,6 +5264,11 @@ entities@^2.0.0:
resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==
+entities@^4.2.0, entities@^4.3.0:
+ version "4.4.0"
+ resolved "https://registry.yarnpkg.com/entities/-/entities-4.4.0.tgz#97bdaba170339446495e653cfd2db78962900174"
+ integrity sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==
+
errno@^0.1.3, errno@~0.1.7:
version "0.1.8"
resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f"
@@ -5953,6 +5992,13 @@ fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6:
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==
+fast-xml-parser@^4.0.15:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.1.1.tgz#28c878b7f1eb4555fa898f1c715adf9d4007306e"
+ integrity sha512-4gAP5PvNyrqePBOIIcpaEeE+nKBry1n6qTQiJsE59sLP0OC+YwhU7/XVmLLEMexbiluFQX1yEYm82Pk9B7xEiw==
+ dependencies:
+ strnum "^1.0.5"
+
fastq@^1.6.0:
version "1.13.0"
resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c"
@@ -6125,7 +6171,7 @@ focus-trap@^6.2.2:
dependencies:
tabbable "^5.3.3"
-follow-redirects@^1.0.0, follow-redirects@^1.14.0:
+follow-redirects@^1.0.0, follow-redirects@^1.14.0, follow-redirects@^1.15.0:
version "1.15.2"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
@@ -6157,6 +6203,15 @@ form-data@^3.0.0:
combined-stream "^1.0.8"
mime-types "^2.1.12"
+form-data@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
+ integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
+ dependencies:
+ asynckit "^0.4.0"
+ combined-stream "^1.0.8"
+ mime-types "^2.1.12"
+
forwarded@0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
@@ -6659,6 +6714,16 @@ htmlparser2@^6.1.0:
domutils "^2.5.2"
entities "^2.0.0"
+htmlparser2@^8.0.1:
+ version "8.0.1"
+ resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-8.0.1.tgz#abaa985474fcefe269bc761a779b544d7196d010"
+ integrity sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==
+ dependencies:
+ domelementtype "^2.3.0"
+ domhandler "^5.0.2"
+ domutils "^3.0.1"
+ entities "^4.3.0"
+
http-deceiver@^1.2.7:
version "1.2.7"
resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87"
@@ -10347,6 +10412,11 @@ proxy-addr@~2.0.7:
forwarded "0.2.0"
ipaddr.js "1.9.1"
+proxy-from-env@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
+ integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
+
prr@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476"
@@ -11196,6 +11266,14 @@ rollup@^1.31.1:
"@types/node" "*"
acorn "^7.1.0"
+rss-to-json@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/rss-to-json/-/rss-to-json-2.1.1.tgz#a870852e33525a512a143409cbf8763af6e9a255"
+ integrity sha512-xXf93iDpOadPWMtAUA/bT1iyifscdbIdtAQBGRbXLfnDQ5Z4tWpmbrAhV3p54nWxwm4joGIDeUrj6CLGm8BI3A==
+ dependencies:
+ axios "^1.2.6"
+ fast-xml-parser "^4.0.15"
+
rsvp@^4.8.4:
version "4.8.5"
resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734"
@@ -11965,6 +12043,11 @@ strip-json-comments@^3.1.0, strip-json-comments@^3.1.1:
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
+strnum@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/strnum/-/strnum-1.0.5.tgz#5c4e829fe15ad4ff0d20c3db5ac97b73c9b072db"
+ integrity sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==
+
style-loader@1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-1.3.0.tgz#828b4a3b3b7e7aa5847ce7bae9e874512114249e"