A React-based cross-platform news app that delivers ultra-short (β60-word) news stories with swipeable cards. Built with React + Capacitor for web and native mobile deployment.
| Landing Page | Genre Selection |
|---|---|
![]() |
![]() |
| News Feed | Settings |
|---|---|
![]() |
![]() |
- Cross-Platform: Runs on Web, Android (APK/AAB), and iOS (planned) via Capacitor 5
- Ultra-Short Summaries: News stories condensed to ~60 words β no fluff, just facts
- TikTok-style Swipe: Two-card stack with
translateYtransitions, double-rAF two-phase animation, 380ms cubic-bezier easing β swipe or use arrow keys - Real-time News: Live Google News RSS feeds, auto-refreshes on country or genre change without a full page reload
- Infinite Scroll: Automatically appends more articles (deduped by URL) when within 3 cards of the end
- Reading Time: Per-article word-count estimate displayed on each card (~200 wpm)
- Dark / Light Mode: Toggle on every screen β Landing, Genre Selection, Feed, and Settings; defaults to light, persisted to localStorage
- Redesigned Landing Page: Hero layout with 2-column feature grid (Zap / Globe2 / Bookmark / ShieldCheck), theme toggle, and Skip button
- Redesigned Genre Selection: Dark 2-column tile grid with per-genre gradient colors, animated checkmark badge on selected tiles, scale bump, sticky progress bar + CTA
- Redesigned Settings Page: Profile strip with gradient icon, grouped Section cards (rounded-2xl), SettingRow with colored icon pills + Toggle switch, bottom-sheet backdrop-blur modals
- Swipe-proof Header: Touch events outside the card are captured to prevent the header from being dragged away β inner text panel remains independently scrollable
- Debounced Search: 400ms debounce so results only filter after you stop typing
- Article / Newspaper Toggle: Switch between searching article titles+descriptions vs. source names (e.g. "BBC", "Reuters", "The Hindu")
- Inline No-Results Fallback: When a search yields zero results, a friendly card replaces the stack with hint text and quick-action buttons (Clear search / Switch mode) β you never leave the feed
- Typing Indicator: "Searchingβ¦" overlay shown during the debounce window
- Country Selection: 15+ countries with flag indicators β news scoped to your region, passed server-side via
countryParam - Genre Selection: 8 categories β Technology, World, Business, Sports, Science, Health, Entertainment, Politics
- Default Sort: Toggle between Personalized feed or Latest news
- Hide Paywalled Articles: Filter out articles from 30 known paywalled sources (expanded from 9)
- Font Size: Small / Medium / Large reading preference
- Wikipedia Image Pipeline: MediaWiki search+pageimages API with keyword extraction, financial stop-word filtering, per-session module-level cache, and
CapacitorHttpon Android native to bypass WebView CORS
- Bookmarking: Save articles to a dedicated Bookmarks page, persisted to localStorage
- Comments System: Inline name prompt, 500-character limit with live counter, like deduplication keyed by
hashKeyβ nowindow.prompt(), no repeat likes - Share Functionality: Native share sheet on mobile, clipboard fallback on web
- Read Aloud:
SpeechManagersingleton (Web Speech API TTS) β listen to articles hands-free
- Bias Analysis: Community bias panel + vote sheet keyed by
hashKey - Enhanced Bias Mode: When enabled in Settings, auto-runs
/api/ai/bias-analysison every article load and pre-fills the score
- Error Boundary: React class component wraps the entire app β catches render crashes, shows "Try again" UI
hashKey(djb2): AlllocalStoragekeys derived from article IDs use a collision-resistant djb2 hash instead ofbtoa()β safe against special characters and encoding collisions- Serverless-First Fetching:
api.jstries/api/news(Vercel serverless, true server-side CORS bypass) first; falls back toallorigins.winCORS proxy for local dev / fallback - Progress Indicator:
X / Ycounter below the card stack
- Appearance: Dark/light theme toggle
- Feed: Hide paywalled (30 domains), default sort mode, Enhanced Bias Analysis
- Notifications: On/off toggle
- Account: Bookmarks, Subscriptions (stub), Clear History, Feedback, Help & Support
- Danger Zone: Log out (clears localStorage)
| Date | Version | Updates |
|---|---|---|
| 19/03/2026 | v3.1.0 | π Search, Reliability & UX Polish β’ Debounced search (400 ms) with Article / Newspaper toggle - filter by title+description or by source name. β’ Inline no-results fallback - friendly card with Clear search and Switch mode CTAs replaces card stack; never kicks user out of the feed. β’ Typing indicator overlay during debounce window. β’ ErrorBoundary component wraps entire app - catches render crashes with Try again UI. β’ hashKey (djb2) replaces btoa() for all localStorage keys - collision-resistant, safe against special characters.β’ CommentsCard rewrite - inline name prompt (no window.prompt()), 500-char limit with live counter, like deduplication per article.β’ SpeechManager singleton (speech.js) replaces ad-hoc inline speech code in NewsCard.β’ Reading time estimate per article (word-count / 200 wpm). β’ Enhanced Bias auto-run - when enabled in Settings, bias score fetched automatically on article load. β’ Serverless-first fetch - api.js now tries /api/news (Vercel, true server-side bypass) before CORS proxy fallback.β’ Country-aware serverless - api/news.js accepts countryParam query param; builds country-scoped RSS URLs server-side.β’ Infinite scroll - auto-appends more articles (URL-deduped) when within 3 cards of end. β’ Progress indicator - X / Y counter below card stack. β’ No reload on country change - loadNews(forceRefresh, countryOverride) replaces window.location.reload().β’ Swipe-proof header - touchAction: none plus gesture capture on main prevents header from being swiped away; inner text panel independently scrollable.β’ Paywall list expanded 9 to 30 domains (Telegraph, Wired, HBR, Nature, Barrons, regional papers, AFR, etc.). β’ Dead code removed: HeaderBar.jsx, SettingsModal.jsx, UnderConstruction.jsx. |
| 19/03/2026 | v3.1.0 | π Search, Reliability & UX Polish β’ Debounced search (400 ms) with Article / Newspaper toggle - filter by title+description or by source name. β’ Inline no-results fallback - friendly card with Clear search and Switch mode CTAs replaces card stack; never kicks user out of the feed. β’ Typing indicator overlay during debounce window. β’ ErrorBoundary component wraps entire app - catches render crashes with Try again UI. β’ hashKey (djb2) replaces btoa() for all localStorage keys - collision-resistant, safe against special characters.β’ CommentsCard rewrite - inline name prompt (no window.prompt()), 500-char limit with live counter, like deduplication per article.β’ SpeechManager singleton (speech.js) replaces ad-hoc inline speech code in NewsCard.β’ Reading time estimate per article (word-count / 200 wpm). β’ Enhanced Bias auto-run - when enabled in Settings, bias score fetched automatically on article load. β’ Serverless-first fetch - api.js now tries /api/news (Vercel, true server-side bypass) before CORS proxy fallback.β’ Country-aware serverless - api/news.js accepts countryParam query param; builds country-scoped RSS URLs server-side.β’ Infinite scroll - auto-appends more articles (URL-deduped) when within 3 cards of end. β’ Progress indicator - X / Y counter below card stack. β’ No reload on country change - loadNews(forceRefresh, countryOverride) replaces window.location.reload().β’ Swipe-proof header - touchAction: none plus gesture capture on main prevents header from being swiped away; inner text panel independently scrollable.β’ Paywall list expanded 9 to 30 domains (Telegraph, Wired, HBR, Nature, Barrons, regional papers, AFR, etc.). β’ Dead code removed: HeaderBar.jsx, SettingsModal.jsx, UnderConstruction.jsx. |
| 15/03/2026 | v3.0.0 | π¨ Full UI Overhaul & Bug Fixes β’ TikTok-style swipe animation: two-card stack with translateY transitions, double-rAF two-phase animation, 380 ms cubic-bezier easing.β’ Wikipedia Smart Images: switched from page/summary to MediaWiki search+pageimages API; keyword extraction strips financial stop words; per-session module-level cache; CapacitorHttp on Android native to bypass WebView CORS.β’ Flicker & white-flash fixes: removed synchronous setResolvedImage(null) and isTransitioning opacity animation from Feed.β’ Country dropdown z-index fix: Feed header gets relative z-50 so selector renders above card stack.β’ Landing page redesign: dark/light hero layout, 2-column feature grid (Zap / Globe2 / Bookmark / ShieldCheck icons), sun/moon toggle, Skip button. β’ Genre Selection redesign: dark 2-column square tile grid with per-genre gradient colors, checkmark badge on selected tiles, scale bump, sticky progress bar + CTA. β’ Settings page redesign: Profile strip with gradient icon, grouped Section cards (rounded-2xl), SettingRow with colored icon pills + Toggle switch, bottom-sheet backdrop-blur modals; sections: Appearance / Feed / Notifications / Account / Support / Danger zone. β’ Theme toggle added to Landing and Genre Selection (uses existing ThemeContext, defaults to light). β’ ~15 miscellaneous bug fixes (Bengali locale, Android build, image cache race conditions, etc.) |
| 16/08/2025 | v2.3.0 | π¨ Layout Refresh & Settings Expansion β’ New compact header in Feed with country selector, refresh, sort (Personalized/Latest), theme and settings buttons. β’ βSwipe upβ affordance and smoother card transitions. β’ Read Aloud controls and keyboard shortcuts (Arrow Up/Down, Ctrl+Space). β’ Community Bias features: analysis panel + vote sheet with local persistence. β’ Article translation with LibreTranslate/Lingva fallback. β’ Settings additions: Theme, Notifications, Reading font size, Hide paywalled, Default sort, Bookmarks, Subscriptions (stub), Clear history, Feedback, Help & Support, Logout |
| 16/08/2025 | v2.2.0 | πΌοΈ Smart Image Resolver & Reliability β’ Prefer original article URL (bypass Google News redirect). β’ Extract images from OG/Twitter/JSONβLD/srcset and follow canonical links. β’ Openverse photograph fallback when no image is found. β’ Web image proxying for reliability (Weserv). β’ Improved handling of placeholder/flag images |
| 30/01/2025 | v2.1.0 | π Country Selection Feature β’ Added 15+ country support with flag indicators. β’ Auto-refresh on country change. β’ Visual loading states for country selector. β’ Improved refresh button feedback |
| 29/01/2025 | v2.0.0 | π§ Major UI/UX Improvements β’ Fixed mobile external URL navigation. β’ TikTok-style swipe navigation. β’ Dark/Light theme toggle. β’ Comments system with local storage. β’ Responsive design for mobile/desktop. β’ Share functionality. β’ Capacitor integration for mobile apps. β’ Mobile "Read More" button fixes |
| 28/01/2025 | v1.5.0 | π± Mobile Optimization β’ Enhanced swipe gestures. β’ Improved touch responsiveness. β’ Better mobile UI components |
| 27/01/2025 | v1.0.0 | π Initial Release β’ Core news fetching functionality. β’ Genre selection. β’ Basic UI components. β’ Web deployment ready |
flowchart TD
A(["π€ User"])
A --> Landing
subgraph Pages["ποΈ Pages β React Router v6"]
direction TB
Landing["π Landing\nHero Β· Feature Grid Β· Theme Toggle"]
Genre["π― GenreSelection\n2-col Gradient Tile Grid Β· Progress Bar"]
Feed["π° Feed\nTikTok Swipe Stack Β· Country Selector"]
Bookmarks["π Bookmarks\nSaved Articles List"]
Settings["βοΈ Settings\nSections Β· Toggles Β· Bottom-sheet Modals"]
Landing -->|"Get Started"| Genre
Genre -->|"Confirm genres"| Feed
Feed --> Bookmarks
Feed --> Settings
end
subgraph Components["π§© Shared Components"]
direction TB
NewsCard["π NewsCard\nSwipe Β· Reading Time Β· Bias Auto-run"]
ErrorBoundary["π‘οΈ ErrorBoundary\nRender Error Catch Β· Retry UI"]
CountrySelector["π CountrySelector\nFlag Dropdown β z-50 above stack"]
CommentsCard["π¬ CommentsCard\nInline Name Β· 500-char Limit Β· Dedup"]
end
subgraph State["ποΈ Global State β React Context"]
direction LR
ThemeCtx["π ThemeContext\nisDark / toggleTheme\nβ localStorage"]
BookmarkCtx["π BookmarkContext\nbookmarks[]\nβ localStorage"]
end
subgraph Utils["π οΈ Utils & Services"]
direction LR
apiJs["api.js\nserverless-first fetchNews()"]
mockApi["mockApi.js\nXML β article[] Β· getCountryParam()"]
storageJs["storage.js\nget/set helpers Β· hashKey (djb2)"]
speechJs["speech.js\nSpeechManager singleton"]
genresJs["genres.js\nGradient map"]
end
Feed --> NewsCard
Feed --> CountrySelector
NewsCard --> CommentsCard
App --> ErrorBoundary
Feed --> apiJs
apiJs --> mockApi
Settings --> ThemeCtx
Settings --> storageJs
Feed --> BookmarkCtx
classDef page fill:#dbeafe,stroke:#2563eb,color:#0f172a,rx:8
classDef component fill:#dcfce7,stroke:#16a34a,color:#0f172a,rx:8
classDef ctx fill:#fef9c3,stroke:#ca8a04,color:#0f172a,rx:8
classDef util fill:#ede9fe,stroke:#7c3aed,color:#0f172a,rx:8
classDef user fill:#f1f5f9,stroke:#475569,color:#0f172a,rx:20
class Landing,Genre,Feed,Bookmarks,Settings page
class NewsCard,ErrorBoundary,CountrySelector,CommentsCard component
class ThemeCtx,BookmarkCtx ctx
class apiJs,mockApi,storageJs,speechJs,genresJs util
class A user
flowchart TD
subgraph Fetch["π‘ News Fetching β api.js"]
direction TB
Check{"Running on\nnative platform?"}
Serverless["POST /api/news\nVercel serverless (tried first)"]
CapHttp["CapacitorHttp.get\nBypasses WebView CORS"]
Proxy["fetch β allorigins.win\nCORS proxy fallback"]
Check -->|"Android / iOS"| CapHttp
Check -->|"Web β try serverless"| Serverless
Serverless -->|"fails"| Proxy
end
subgraph RSS["βοΈ Google News RSS"]
direction TB
GRSS["news.google.com/rss\nper country hl/gl param\n+ genre topic URL"]
end
subgraph Images["πΌοΈ Wikipedia Image Pipeline β NewsCard"]
direction TB
Query["buildWikiQuery\nstrip tickers + stop-words\ntop-5 keywords"]
Wiki["MediaWiki API\nsearch + pageimages"]
Cache["resolvedImageCache\nmodule-level Map\n(per session)"]
Query --> Wiki --> Cache
end
subgraph Serverless["βοΈ Vercel Serverless"]
direction TB
NewsAPI["api/news.js\nServer-side RSS fetch"]
BiasAPI["api/ai/bias-analysis.js\nAI bias scoring"]
end
subgraph Platform["π² Platform Layer β Capacitor 5"]
direction LR
Web["π Web\nVite dist Β· Vercel CDN"]
Android["π€ Android\nAPK / AAB Β· CapacitorHttp"]
iOS["π iOS\nplanned"]
end
CapHttp --> GRSS
Proxy --> GRSS
NewsAPI -.->|server-side bypass| GRSS
Fetch --> RSS
Images -.->|image lookup| Wiki
Platform --> Fetch
Platform --> Images
classDef fetch fill:#dbeafe,stroke:#2563eb,color:#0f172a,rx:8
classDef rss fill:#fff7ed,stroke:#ea580c,color:#0f172a,rx:8
classDef img fill:#dcfce7,stroke:#16a34a,color:#0f172a,rx:8
classDef server fill:#fef9c3,stroke:#ca8a04,color:#0f172a,rx:8
classDef platform fill:#f1f5f9,stroke:#475569,color:#0f172a,rx:8
class Check,CapHttp,Proxy,Serverless fetch
class GRSS rss
class Query,Wiki,Cache img
class NewsAPI,BiasAPI server
class Web,Android,iOS platform
flowchart TD
A([User opens app]) --> B[Landing.jsx\nhero + feature grid]
B -->|Get Started| C[GenreSelection.jsx\nselect 1β8 categories]
C -->|Confirm β genres saved\nto localStorage| D[Feed.jsx\nTikTok swipe stack]
D --> E{fetchNews\ngenre + country}
E --> F{Running on\nnative platform?}
F -->|Yes β Android| G[CapacitorHttp.get\nbypasses WebView CORS]
F -->|No β Web| H[fetch via\nallorigins.win proxy]
G --> I[Google News RSS]
H --> I
I --> J[mockApi.js\nparseXML β article array]
J --> K[Render NewsCard stack\ntwo-card z-indexed]
K --> L{User action}
L -->|Swipe up / Arrowβ| M[Next article\ndouble-rAF translateY\n380ms cubic-bezier]
L -->|Swipe down / Arrowβ| N[Prev article\nsame animation]
L -->|Tap Bookmark| O[BookmarkContext\nβ localStorage]
L -->|Tap Share| P[navigator.share\nor clipboard]
L -->|Long-press / Comments| Q[CommentsCard\nlocal votes]
L -->|Tap Read More| R[Open article URL\nin browser / app]
K --> S[Wikipedia Image Pipeline]
S --> T[buildWikiQuery\nstrip tickers + stop-words\ntop-5 keywords]
T --> U{Platform?}
U -->|Native| V[CapacitorHttp β MediaWiki\nsearch+pageimages API]
U -->|Web| W[fetch β MediaWiki\nsearch+pageimages API]
V --> X[resolvedImageCache\nmodule-level Map]
W --> X
X --> Y[img src in NewsCard]
D --> Z[CountrySelector\nflag dropdown z-50]
Z -->|setSelectedCountry| E
classDef page fill:#e0f2fe,stroke:#2563eb,color:#0f172a
classDef decision fill:#fef9c3,stroke:#ca8a04,color:#0f172a
classDef external fill:#fff7ed,stroke:#ea580c,color:#0f172a
classDef action fill:#f0fdf4,stroke:#16a34a,color:#0f172a
classDef storage fill:#f5f3ff,stroke:#7c3aed,color:#0f172a
class A,B,C,D,K page
class E,F,L,U decision
class I,V,W,R external
class M,N,O,P,Q,S,T,X,Y action
class O,X storage
flowchart TD
A([Touch / Key event]) --> B{Direction?}
B -->|Up / ArrowUp| C[pendingIndexRef = currentIndex + 1]
B -->|Down / ArrowDown| D[pendingIndexRef = currentIndex - 1]
C --> E[setAnimating true\nsetAnimDir up]
D --> F[setAnimating true\nsetAnimDir down]
E --> G[rAF 1 β layout paint]
F --> G
G --> H[rAF 2 β setAnimReady true\ntrigger CSS transition]
H --> I[translateY current card:\nup β -110vh down β +110vh\n380ms cubic-bezier 0.4,0,0.2,1]
I --> J[onTransitionEnd]
J --> K[setCurrentIndex = pendingIndexRef\nsetAnimating false\nsetAnimReady false]
K --> L[New card snaps into place]
classDef anim fill:#e0f2fe,stroke:#2563eb,color:#0f172a
classDef trigger fill:#f0fdf4,stroke:#16a34a,color:#0f172a
classDef state fill:#fef9c3,stroke:#ca8a04,color:#0f172a
class A,B trigger
class C,D,E,F,G,H,I anim
class J,K,L state
erDiagram
APP_STATE {
string newsly-theme "light | dark"
string newsly-genres "JSON array of selected genre ids"
string newsly-country "country code e.g. us, in, gb"
string newsly-sort "personalized | latest"
}
ARTICLE_INTERACTIONS {
string newsly-bookmarks "JSON array of article objects"
string newsly-comments "JSON map hashKey(articleId) β comment[]"
string newsly-bias-votes "JSON map hashKey(articleId) β vote"
string newsly-liked-comments "JSON map hashKey(articleId) β Set of liked comment ids"
string newsly-reading-history "JSON array of viewed article ids"
}
PREFERENCES {
string newsly-notifications "true | false"
string newsly-font-size "small | medium | large"
string newsly-hide-paywalled "true | false"
string newsly-enhanced-bias "true | false"
}
APP_STATE ||--o{ ARTICLE_INTERACTIONS : "drives feed for"
APP_STATE ||--o{ PREFERENCES : "combined with"
- React 18 - UI framework with hooks
- Vite - Build tool and dev server
- React Router - Client-side routing
- Tailwind CSS - Utility-first styling
- Lucide React - Icon library
- Framer Motion - Animations (optional)
- Capacitor - Cross-platform native runtime
- Capacitor HTTP - Native HTTP requests
- Android SDK - Android app compilation
- Xcode - iOS app compilation
- Google News RSS - News data source
- XML Parser - RSS feed processing
- CORS Proxy - Web development (allorigins.win)
- Country API - Country-specific news feeds
- localStorage - Client-side data persistence
- No Database - Fully client-side application
newsly/
β
βββ src/ # React application source
β β
β βββ App.jsx # Root component β React Router route definitions
β βββ main.jsx # Entry point β mounts React + global CSS
β βββ index.css # Tailwind base imports + custom global styles
β β
β βββ pages/ # Full-screen route pages
β β βββ Landing.jsx # Hero onboarding β feature grid, dark/light toggle
β β βββ GenreSelection.jsx # 2-col gradient tile grid, progress bar, theme toggle
β β βββ Feed.jsx # News feed β TikTok swipe stack, country selector
β β βββ Settings.jsx # Full-page settings β sections, toggles, modals
β β βββ Bookmarks.jsx # Saved articles list
β β
β βββ components/ # Reusable UI building blocks
β β βββ NewsCard.jsx # Article card β image pipeline, reading time, bias auto-run
β β βββ CommentsCard.jsx # Inline comments β name prompt, 500-char limit, like dedup
β β βββ CountrySelector.jsx # Flag dropdown (z-50, above card stack)
β β βββ ErrorBoundary.jsx # Class component β render error catch + retry UI
β β
β βββ contexts/ # React context providers
β β βββ ThemeContext.jsx # isDark state, toggleTheme(), localStorage sync
β β βββ BookmarkContext.jsx # bookmarks[], add/remove, localStorage sync
β β
β βββ utils/ # Pure helpers and service modules
β βββ api.js # fetchNews() β serverless-first, CORS proxy fallback
β βββ mockApi.js # XML β article[] parser Β· getCountryParam() export
β βββ genres.js # Genre definitions + GENRE_STYLES gradient map
β βββ storage.js # get/set helpers Β· hashKey(str) djb2 export
β βββ constants.js # PAYWALLED_DOMAINS (30), countries, flags
β βββ speech.js # SpeechManager singleton β TTS read-aloud
β βββ __tests__/
β βββ countryNews.test.js # Jest unit tests for country news fetching
β
βββ api/ # Vercel serverless functions
β βββ news.js # /api/news β server-side RSS fetch (CORS bypass)
β βββ ai/
β βββ bias-analysis.js # /api/ai/bias-analysis β AI bias scoring endpoint
β
βββ android/ # Capacitor Android project (auto-generated)
β βββ app/
β β βββ src/main/
β β β βββ AndroidManifest.xml # Permissions: internet, vibrate
β β β βββ java/com/newsly/app/
β β β β βββ MainActivity.java # Capacitor bridge entry point
β β β βββ res/ # Icons, splash screens, layouts
β β βββ build.gradle
β βββ capacitor.build.gradle
β βββ variables.gradle # SDK version pins
β βββ local.properties # sdk.dir path (machine-local, gitignored)
β
βββ index.html # Vite HTML shell
βββ vite.config.js # Vite build config
βββ tailwind.config.js # Tailwind theme + dark mode: 'class'
βββ postcss.config.js # PostCSS (Tailwind + Autoprefixer)
βββ capacitor.config.json # App ID, webDir, CapacitorHttp plugin config
βββ jest.config.js # Jest test runner config
βββ vercel.json # Vercel routing / function config
βββ package.json # Scripts, dependencies
// Platform Detection
if (window.Capacitor?.isNativePlatform()) {
// Native: Use Capacitor HTTP (bypasses CORS)
const response = await CapacitorHttp.get({
url: googleNewsRssUrl,
headers: { 'User-Agent': 'NewsBot/1.0' }
});
} else {
// Web: Use CORS proxy
const response = await fetch(corsProxyUrl + googleNewsRssUrl);
}const getCountrySpecificUrl = (category, country) => {
const baseUrls = {
'technology': 'https://news.google.com/rss/topics/CAAqJggKIiBDQkFTRWdvSUwyMHZNRFZxYUdjU0FtVnVHZ0pWVXlnQVAB',
'business': 'https://news.google.com/rss/topics/CAAqJggKIiBDQkFTRWdvSUwyMHZNRGx6TVdZU0FtVnVHZ0pWVXlnQVAB',
// ... more categories
};
return country === 'global'
? baseUrls[category]
: `${baseUrls[category]}?hl=${country}&gl=${country.toUpperCase()}`;
};// XML to JSON conversion with country support
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(xmlText, 'text/xml');
const items = xmlDoc.querySelectorAll('item');
// Extract article data
const articles = Array.from(items).map(item => ({
title: item.querySelector('title')?.textContent,
description: cleanDescription(item.querySelector('description')?.textContent),
link: item.querySelector('link')?.textContent,
pubDate: new Date(item.querySelector('pubDate')?.textContent),
category: category,
country: selectedCountry,
id: generateUniqueId()
}));- Node.js 18+ (Current: v20.16.0 supported)
- Android Studio (for Android development)
- Xcode (for iOS development, macOS only)
- Clone the repository:
git clone <your-repo-url>
cd newsly- Install dependencies:
npm install- Start development server:
npm run dev- Open in browser:
http://localhost:5173
- Add Android platform:
npx cap add android- Build and sync:
npm run build
npx cap sync- Open in Android Studio:
npx cap open android- Run on device/emulator:
npm run android- Add iOS platform:
npx cap add ios- Build and sync:
npm run build
npx cap sync- Open in Xcode:
npx cap open ios# Build for web
npm run build
# Preview build
npm run preview
# Deploy to Vercel/Netlify
# Upload dist/ folder- Generate keystore:
keytool -genkey -v -keystore my-release-key.keystore -keyalg RSA -keysize 2048 -validity 10000 -alias my-key-alias- Build signed APK:
npm run build
npx cap sync
npx cap build android --keystorepath ./my-release-key.keystore --keystorepass YOUR_PASSWORD --keystorealias my-key-alias --keystorealiaspass YOUR_ALIAS_PASSWORD --androidreleasetype APK- APK location:
android/app/build/outputs/apk/release/app-release-signed.apk
- Build for iOS:
npm run build
npx cap sync
npx cap open ios- In Xcode:
- Set signing team
- Archive for distribution
- Upload to App Store Connect
{
"appId": "com.newsly.app",
"appName": "Newsly",
"webDir": "dist",
"server": {
"androidScheme": "https",
"cleartext": true,
"allowNavigation": ["*"]
},
"plugins": {
"CapacitorHttp": {
"enabled": true
}
}
}# .env.local (optional, for future API keys)
NEWS_API_KEY=your_api_key_here
VITE_APP_NAME=Newsly- Update countries.js:
export const countries = [
// ... existing countries
{ code: 'de', name: 'Germany', flag: 'π©πͺ' }
];- Country will auto-work with existing RSS feeds
- Update genres.js:
export const genres = [
// ... existing genres
{ id: 'science', name: 'Science', icon: 'π¬', color: 'bg-green-500' }
];- Add RSS URL in api.js:
const categoryUrls = {
// ... existing URLs
'science': 'https://news.google.com/rss/topics/SCIENCE_RSS_URL'
};// tailwind.config.js
module.exports = {
theme: {
extend: {
colors: {
primary: '#your-color',
secondary: '#your-color'
}
}
}
}# Run linting
npm run lint
# Fix linting issues
npm run lint:fix
# Test on different devices
npm run android # Android emulator
npm run ios # iOS simulator (macOS)
npm run dev # Web browser- Bundle Size: ~500KB (gzipped)
- First Load: <2s on 3G
- News Fetch: <1s average
- Country Switch: <500ms
- Offline Support: Cached articles available
- Memory Usage: <50MB on mobile
- No API Keys: Uses public RSS feeds
- HTTPS Only: All requests encrypted
- No User Data: Everything stored locally
- CORS Handled: Proper cross-origin setup
- Content Security: Sanitized HTML content
- Vercel (Recommended)
- Netlify
- GitHub Pages
- Firebase Hosting
- Google Play Store
- Apple App Store
- Direct APK/IPA distribution
- Enterprise deployment
- Fork the repository
- Create feature branch (
git checkout -b feature/amazing-feature) - Commit changes (
git commit -m 'Add amazing feature') - Push to branch (
git push origin feature/amazing-feature) - Open Pull Request
MIT License - see LICENSE file for details.
- Google News - RSS feed data source
- Capacitor - Cross-platform framework
- React Team - UI framework
- Tailwind CSS - Styling system
- Lucide - Icon library
Built with β€οΈ by V
Newsly - Stay informed, stay brief.



