Skip to content

News

Overview

The News module manages articles, publications, and announcements displayed on the public landing page. It is a full CMS: article categories, localized (bilingual) content, rich-text editing, SEO meta tags, featured thumbnails, a live device preview, reader analytics, and a personalized recommendation engine.

The module has two sides:

  • Admin panel — Filament cluster at /portal/news (navigation label News, group Master).
  • Public site — the /articles landing pages plus analytics/API endpoints.

Key Entities

EntityDescription
ArticlePublished news content — localized title/content/excerpt, slug, tags, category, school, author, thumbnail, SEO meta, publish date, active flag
ArticleCategoryCategory grouping for articles — localized name/description, slug, active flag
ArticleAnalyticReading-session analytics per article — fingerprint, duration, scroll depth, completion, device, geo location

Admin Panel (/portal/news)

The cluster contains three areas:

AreaURLPurpose
Articles/portal/news/articlesManage article records (list, create, edit, view)
Article Categories/portal/news/article-categoriesManage categories
Popular Articles/portal/news/popular-articles-pageReader analytics dashboard

1. Articles — List Page

Columns

ColumnNotes
Name (title)Searchable, localized
SchoolShows school name or General placeholder
CategoryCategory name or -
SlugSearchable
AuthorSearchable, - if empty
StatusBadge — Active (green) / Inactive (gray)

Filters

  • Status — ternary filter: Active / Inactive
  • School — select with General option
  • Category — select from existing categories
  • Published Date — date range (from / until)
  • Created Date — date range (from / until)

Global search uses full-text search (Meilisearch) — matches title, content, and excerpt in both languages, plus author and tags, with relevance ranking.

Row actions: View, Edit. Bulk action: Delete (selected rows).

2. Articles — Create / Edit Form

The form is split into a General / Localized content column (span 3) and a Thumbnail / SEO sidebar (span 1).

General section

FieldRequiredNotes
School (optional)NoDefault General — no school
CategoryNoSelect with + create option (inline create: slug + localized name/description)
SlugYesMust be unique (validated) — e.g. registration-guide
AuthorNoFree text, e.g. Admin
Published AtNoDate picker, min date = today (scheduling)
TagsNoComma/enter-separated tag input

Localized content (tab per language, EN/ID)

FieldRequired (base locale)Notes
Name (title)YesPlain text
ExcerptYesTextarea, rendered as an HTML preview on the public detail page
ContentYesHeadless rich-text editor (headings, quotes, media, / quick menu)

The localization tab bar provides an Auto-translate button that copies the base-language content into the current language.

Thumbnail section

FieldNotes
Upload ImageImage upload (public disk, articles/ dir, image editor), stored as an Asset, preview via ImageKit
Alt textMax 150 chars, for accessibility

SEO section

  • Meta tags repeater (modal) — list of {name, content} pairs.
  • name is a select of predefined SEO keys (e.g. description, keywords, og:title, og:image, twitter:...) with + create option for custom names; each name can only be used once (validated by UniqueSeoName).
  • content free text, max 255 chars.

3. Articles — View Page

Displays:

  • General info — slug, category, school, author, status badge, tags
  • Localized values — title, excerpt (rendered as prose/HTML preview), content (rendered from the rich-text editor)
  • Live Preview card — an iframe of the public article page with Desktop / Tablet / Mobile device switcher, framed mockups, responsive scaling, and loading state.

4. Article Categories — Manage Page

Form

FieldRequiredNotes
SlugYesUnique, max 100 chars
ActiveNoSwitcher, default on
Name (localized)YesMax 255 chars
Description (localized)NoTextarea

Table columns: Name, Slug (badge, searchable/sortable), Articles (count per category, sortable), Active (boolean icon), Created Date (sortable, hidden by default, toggleable).

Row actions: Edit, Delete. Sorting default: by slug.

Only Active categories appear on the public site.

  • Stats cards (poll every 60 s): Total Article Categories, Unpublished Articles (null or future published_at), Published Articles (published_at <= now).
  • Reader Geography — treemap chart of reads by country (from IP geo-lookup; "Unknown" bucket).
  • Popular Articles table — per article: Reads, Unique Readers, Total Time (humanized 45s / 3m 20s / 1h 5m), Completed. Default sort: total time desc. Pagination 5 / 10 / 25. Empty state when no analytics data exists yet.

Publication Flow

The following flowchart shows how an article moves from the admin form to the public site:

Key rules

  • An article is publicly visible only when is_active = true and published_at is set and not in the future.
  • Setting a future published_at schedules the article — it stays hidden (unpublished) until that date.
  • Every save invalidates the cached recommendation/analytics data so changes appear immediately.

Public Site

Routes

RoutePageDescription
GET /articlesArticles/IndexBrowse + filter + search articles
GET /articles/{slug}Articles/ShowArticle detail with recommendations
GET /articles/{slug}/iframeArticles/IFrameShowClean embeddable view (no layout chrome), used by the admin preview card

/articles — Listing

  • Search box — full-text search over title/content/excerpt (both languages), author, tags.
  • Filters sidebar — Category (multi-select), Author, Year; Sort: Newest / Oldest / A–Z / Z–A.
  • Result count + Pagination, empty state with reset button, sticky search bar with smooth scroll-back on filter change.
  • Server-side list is capped at 100 most recent active articles; filtering/sorting/pagination happen client-side over that set. Older articles remain reachable by direct URL.
  • Categories load lazily via Inertia::defer (skeleton until ready).

/articles/{slug} — Detail

Only active articles are shown; anything else returns 404.

  • Header: category badge, title, meta (author / published date / read time — hidden when empty), back link.
  • Excerpt rendered as an HTML preview above the content.
  • Body: rich-text content with a read-completion sentinel.
  • Lucky Me interstitial — scroll to trigger a random other article.
  • Sidebar: recommended articles (personalized), related articles by author.
  • Mobile sections: recommendations + same-author articles.
  • SEO: custom meta tags from the admin repeater are rendered as <meta> tags (og:* keys use property, others use name); sensible fallbacks (description, og:title, og:description, og:type, og:image, article:author, article:section, article:published_time, og:url).

/articles/{slug}/iframe

Minimal article view used for embedding (admin preview, third-party embeds).


SEO Flow

The following flowchart shows how meta tags configured in the admin form are rendered on the public article page:

Predefined keys (from the SEO enum): description, keywords, author, robots, og:title, og:description, og:image, og:url, og:type, og:site_name, twitter:card, twitter:title, twitter:description, twitter:image — plus any custom name added via the + create option.

Always-rendered fallbacks on the detail page: og:title, og:type (article), og:url, og:image (if set), article:author, article:section, article:published_time, plus description / og:description derived from the excerpt when no custom SEO tag overrides them.


Analytics

Reading sessions are tracked client-side (start / scroll / completion) and pushed to the server.

Endpoint: POST /api/articles/track

Payload: article_id (required), fingerprint_id (required), session_id, read_duration_seconds, scroll_depth (0–100), completed, referrer_url.

Behavior:

  • updateOrCreate on (article, fingerprint, session) — one session per reader per article.
  • Duration and scroll depth only ever increase (monotonic).
  • IP resolved to country/city/timezone via ip-api.com (cached 24 h, best-effort, skipped for localhost).
  • Device type detected from user-agent (desktop / tablet / mobile).
  • Completion is flagged when the reader scrolls past the article-end sentinel (IntersectionObserver).

Analytics flow — from reading session to dashboard


Recommendation Engine

RoundupService produces the "recommended articles" list (default 8) on the detail page.

Flow

Base score (cached in Redis 10 min, shared across visitors):

SignalWeight
Same category0.35
Same school0.15
Popularity (reads)0.15
Recency (decay over 6 months)0.10

Personalization (applied per fingerprint on top of the cached base):

SignalWeight
Collaborative — readers of this article also read...up to 0.25
This user's category historyup to 0.15

Any article create / update / delete invalidates the cached roundups and analytics (ArticleObserver).


Database

TableDescription
articlesUUID PK; title/content/excerpt (localized JSON), slug (unique, route key), tags, category_id, school_id, author, is_active, published_at, image_asset_id, asset_alt_text, meta (SEO JSON)
article_categoriesUUID PK; localized name/description, slug, is_active
article_analyticsUUID PK; article_id, fingerprint_id, session_id, ip_address, user_agent, referrer_url, started_at, last_active_at, read_duration_seconds, scroll_depth, completed, device_type, country, city, timezone

Features

Admin — Article Management

  • CRUD — create, edit, view, and delete articles; bulk delete from the list.
  • Bilingual content — localized title, excerpt, and content (EN/ID) with per-language tabs.
  • Auto-translate — copy the base-language content into the other language with one click.
  • Rich-text editor — headings, subtitles, quotes, links, and a / quick menu.
  • Insert media in content — upload and embed images directly inside the article body.
  • Thumbnail — upload a featured image (with image editor) plus accessibility alt text, served via ImageKit.
  • SEO meta tags — repeater of {name, content} pairs with predefined + custom keys and unique-name validation; rendered as <meta> tags on the public page.
  • Scheduling — set a future publish date; the article stays unpublished until then.
  • Tags — free-form tag input on the article.
  • Categories — manage categories with localized names, or create one inline from the article form.
  • Search & filters — full-text search (Meilisearch) plus status/school/category/date-range filters.
  • Live device preview — view the public article inside desktop/tablet/mobile frames.

Admin — Analytics Dashboard

  • Stats overview — total categories, published, and unpublished article counts.
  • Reader Geography — treemap of reads by country.
  • Popular Articles — reads, unique readers, total reading time, and completions per article.

Public — Articles

  • Listing — browse /articles with search, category/author/year filters, sort (newest/oldest/A–Z/Z–A), pagination, and empty state.
  • Article detail — category badge, title, author/date/read-time meta, excerpt as an HTML preview, and the rich-text body.
  • Recommendations — a personalized "recommended" list plus same-author related articles.
  • Lucky Me — scroll interstitial that jumps to a random article.
  • SEO — custom meta tags, Open Graph tags, and fallbacks rendered in <head>.
  • Localization — switch EN ↔ ID on the fly without a reload.
  • Embeddable view — clean /articles/{slug}/iframe used for embedding and the admin preview.

Analytics (tracking)

  • Reading sessions — track start, duration, scroll depth, and completion per reader.
  • Geo-location — country/city/timezone resolved from IP (cached, best-effort).
  • Device detection — desktop / tablet / mobile from the user-agent.

Key Files

FilePurpose
app/Filament/Clusters/Article/ArticleCluster.phpCluster definition — slug news, navigation
app/Filament/Clusters/Article/Resources/Articles/ArticleResource.phpArticle resource — pages, localized data mutate/fill
app/Filament/Clusters/Article/Resources/Articles/Schemas/ArticleForm.phpArticle create/edit form
app/Filament/Clusters/Article/Resources/Articles/Schemas/ArticleInfolist.phpArticle view page + live preview card
app/Filament/Clusters/Article/Resources/Articles/Tables/ArticlesTable.phpArticle list — columns, filters, Meilisearch search
app/Filament/Clusters/Article/Resources/ArticleCategories/ArticleCategoryResource.phpCategory CRUD
app/Filament/Clusters/Article/Pages/PopularArticlesPage.phpAnalytics dashboard page
app/Filament/Clusters/Article/Resources/Articles/Widgets/*.phpStats, popular table, geography treemap widgets
app/Filament/Schemas/Components/Cards/ArticlePreview.phpLive device preview component
app/Http/Controllers/Landing/ArticleController.phpPublic index / show / iframe
app/Http/Controllers/Api/ArticleAnalyticController.phpSession tracking endpoint
app/Http/Controllers/Api/ArticleContentController.phpRaw content fetch
app/Http/Controllers/Api/ArticleLuckyController.phpRandom-article endpoint
app/Services/News/RoundupService.phpRecommendation scoring + personalization
app/Services/News/AnalysisService.phpAnalytics aggregations (cached)
app/Observers/News/ArticleObserver.phpCache invalidation on article changes
app/Models/News/Article/Article.phpArticle model
app/Models/News/ArticleCategory/ArticleCategory.phpCategory model
app/Models/News/ArticleAnalytic/ArticleAnalytic.phpAnalytics model
resources/js/pages/landing/Articles/*.sveltePublic listing / detail / iframe pages
resources/js/modules/landing/articles/Frontend stores, filters, analytics tracking, recommendation UI

Glossary

TermDefinition
ArticleA news entry managed in the admin panel and shown on the public site
SlugURL-friendly unique identifier of an article or category (e.g. registration-guide); used as the route key — an article is reachable at /articles/{slug}
ExcerptShort summary of the article (localized, per language) shown as an HTML preview on the public detail page and used as the meta-description fallback
ContentThe main body of the article, written in a rich-text editor (localized, per language)
Published At (published_at)The date/time the article becomes (or became) public; if set in the future the article stays hidden until that moment
Is Active (is_active)Flag controlling public visibility — only active articles appear on /articles and in search
TagsFree-form keywords attached to an article; searchable via full-text search
CategoryGrouping that organizes articles; categories are localized and can be active/inactive
ThumbnailFeatured image of the article (an Asset); shown in cards and used for og:image
Alt textAccessibility description for the thumbnail image
Meta (meta)JSON column on the article holding the SEO repeater data (meta.seo — list of {name, content} pairs)
SEO meta tagA single {name, content} entry configured in the admin repeater, rendered as <meta> on the public page (og:* keys use property)
LocalizationSupport for the same field in multiple languages (EN/ID) stored as JSON; the public page re-localizes without a reload
Fingerprint (fingerprint_id)Stable client-side identifier (cookie/generated) that identifies a reader across sessions without an account; used for analytics and recommendation personalization
Session (session_id)Identifier for one reading session of an article by a reader
Read duration (read_duration_seconds)Total seconds a reader spent on an article; only ever increases (monotonic)
Scroll depth (scroll_depth)How far (0–100%) the reader scrolled through the article
Completed (completed)Flag set when the reader scrolls past the article-end sentinel — used for completion metrics
SentinelA hidden element at the end of the article body observed via IntersectionObserver to detect completion
ArticleAnalyticOne row per article reading session, aggregating the metrics above
Lucky MeScroll-triggered interstitial on the detail page that navigates to a random other article
Recommended articlesA personalized list generated by RoundupService (category/school/popularity/recency + collaborative/history signals)
Reader GeographyDashboard treemap of reads grouped by country, resolved from the reader's IP
MeilisearchFull-text search engine backing the admin global search and the public search box