Skip to content

refactor: make internal editor code more generic#3344

Open
ahtesham-quraish wants to merge 14 commits into
mainfrom
ahtesham/tiptap-front-branch
Open

refactor: make internal editor code more generic#3344
ahtesham-quraish wants to merge 14 commits into
mainfrom
ahtesham/tiptap-front-branch

Conversation

@ahtesham-quraish
Copy link
Copy Markdown
Contributor

@ahtesham-quraish ahtesham-quraish commented May 14, 2026

What are the relevant tickets?

https://github.com/mitodl/hq/issues/11232

Description (What does it do?)

Step 2: Frontend editor refactor (Second PR)

Make page-components/TiptapEditor/ content-type-agnostic. Success looks like:

  • 1. The editor shell, save/publish/draft UI, image upload, schema validation, learning-resource drawer, and toolbar plumbing all live in one place and accept per-type configuration (schema, initial doc, route prefix, theme, extension list).
  • 2. The current news editor becomes a thin composition of the generic shell + a news config module. No visible behavior changes to /news pages.
  • 3. a new UI under /articles that mirrors /news for now, with minimal themeing changes. More changes to come, but start similar.

Screenshots (if appropriate):

  • Desktop screenshots
  • Mobile width screenshots

How can this be tested?

For testing you need to first take pull of this branch and then run the containers like web and celery and frontend, then you need to visit the following url http://open.odl.local:8062/news/new, you first create the article and check if the request is being made to http://api.open.odl.local:8065/api/v1/website_content/ url, basically, you need to test every corner of the articles CRUD and verify everything looks good.

You should keep in mind that when article is created then celery task is run which sync the article to news, so It should run to and you will be able to see the all the articles using the following urls http://open.odl.local:8062/news
http://open.odl.local:8062/website_content/article/new

Additional Context

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 14, 2026

OpenAPI Changes

No changes detected

View full changelog

Unexpected changes? Ensure your branch is up-to-date with main (consider rebasing).

@ahtesham-quraish ahtesham-quraish marked this pull request as ready for review May 14, 2026 12:24
@ahtesham-quraish ahtesham-quraish changed the title refactor: make internal editor code more generic (Second Part) refactor: make internal editor code more generic May 14, 2026
@ahtesham-quraish ahtesham-quraish force-pushed the ahtesham/tiptap-refactor branch 7 times, most recently from f462266 to 69b6536 Compare May 18, 2026 15:29
Copy link
Copy Markdown
Contributor

@ChristopherChudzicki ChristopherChudzicki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ahtesham-quraish I haven't actually tried creation/editing flows on this branch yet, but some feedback on the code is below.

TipTap Editor Organization

Overall, I think we need to strive to better separate news + article. Currently, we've created the /TiptapEditor/contentTypes directory with article and news, but:

  • newsExtensions = everything
  • articleExtensions = just alias of news

We should think about how extensions group:

Tier Criterion Where Examples
1 Every content type needs it (now & likely future) and no per-type config extensions/baseExtensions.ts — one factory StarterKit, Placeholder, Heading, Image*, Divider, MediaEmbed*, LearningResource*, …
2 Shared, but takes per-type config extensions/node/*, composed into each type's list Banner (breadcrumb, background, dark/light), Byline (share-URL prefix — hardcoded /news/ today, ArticleByLineInfoBar.tsx:120)
3 Specific to one content type contentTypes/<type>/ Document expression, seed doc, the per-type config object

Tier-1 test: constructible from only the (uploadHandler, setUploadError) runtime deps, no type-specific arg. Don't pre-split it for hypothetical future exclusions. Net effect: each create<Type>Extensions collapses to [ <Type>Document, ...createBaseExtensions(deps), Banner, Byline ] + seed + config.

All the generic extensions should go somewhere common, something like, maybe inside /TiptapEditor/extensions/baseExtensions.ts.

Other notes:

  1. ArticleByLineInfoBar this should be renamed; its name dates from the olden days when we called news pages were called articles. I think it could just be ByLineInfoBar?
  2. Re GenericEditor: Minor, but Copilot suggested calling this WebsiteContentEditor... it's not truly generic, it is specific to website content. So i think that's a good name.
  3. ArticleProvider / useArticle look like naming debt; WebsiteContentProvider / useWebsiteContent would better match the actual WebsiteContent payload.

Regarding NextJS Routes

You added a bunch of new NextJS routes... e.g., /articles, /articles/new, etc. If we continue with that pattern, every time we add a new content_type, we will need to add a whole bunch of new route files.

I see two groups of routes:

  • User Facing: /news, /news/:id, /articles, /articles/:id ... these are user-facing; adding new route files seems reasonabl
  • Internal for editing: the /new, /[id]/edit, /drafts routes.

I think we should make the editing routes generic: /website_content/[type]/new, /website_content/[type]/[id-or-slug]/edit, /website_content/drafts?content_type=whatever routes for editing call content types.

That way, when we add new content types, we don't need to add new editing routes.

An issue with redirect.

Did you try the new /articles, /articles/new, etc routes that you added? These currently redirect to /news, due to our nextjs config. (From https://github.com/mitodl/hq/issues/10324)

The website_content routes suggested above avoid the issue, at least for editing URLs.

We'll need to check in with Ferdi about whether or not we can remove the redirect. My recollection is that we added it because things were published at the old /articles/ URL before we were expecting. I'll check data tomorrow. It could be that if no one is visiting that page, we can get rid of it.

@ahtesham-quraish ahtesham-quraish force-pushed the ahtesham/tiptap-front-branch branch from 43b42c0 to f54a776 Compare May 19, 2026 10:34
@ahtesham-quraish ahtesham-quraish force-pushed the ahtesham/tiptap-front-branch branch from f54a776 to 6087067 Compare May 19, 2026 13:08
@ahtesham-quraish
Copy link
Copy Markdown
Contributor Author

@ChristopherChudzicki I have applied the styles for the articles page you should check it too please.

image

Copy link
Copy Markdown
Contributor

@ChristopherChudzicki ChristopherChudzicki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ahtesham-quraish The extension separation is much cleaner / clearer now. Thanks!

I didn't get too far with manually testing the flows, but I am seeing some code issues still. I can revisit manual testing first thing in the morning.

  1. next.config.js redirects ... We can remove these. Per https://github.com/mitodl/hq/issues/11365, we are ok to remove them, and will coordiante with Devops to remove the fastly redirects, too.
  2. Naming: Lots of things still reference article where they shouldn't:
    • ArticleContext.tsx: This file should be WebsiteContentContext.tsx, right? Let's do a real rename rather than alias + deprecated flag. Just rename it. It's simpler. Diff is a little larger, but less confusion.
    • Same comment for everything in the TiptapEditor/extensions/node/ArticleByLineInfoBar directory.
    • GenericEditor: Similarly, statements like import { WebsiteContentEditor } from "../../core/GenericEditor" are confusing. Don't deprecate + alias. Just rename it.
      • Doubly so for this file. It's completely new.
    • ArticleEditor.tsx: There are two ArticleEditor.tsx files. This is confusing.
      • As far as I can tell, TiptapEditor/ArticleEditor.tsx is really the News editor, and exists only for the sake of its sibling, ArticleEditor.happydom.test.tsx which, similarly, is really testing the newseditor.
        • At the very least, let's get rid of TiptapEditor/ArticleEditor.tsx and move the happydom test to be a sibling of NewsEditor.
    • TiptapEditor/useArticleSchema.ts: This seems unused. Just get rid of it?
  3. ByLine ... I'm noticing the the articles byline shows a Share URL pointing to news, see screenshot below. It doesn't seem to be visible in the published url, just on the editing view? We should have the correct url though.
  4. Editing URLS: Good that /articles/new, etc, are thin wrappers of the website content versiosn. IMO, thogh, we should just drop /articles/new and /news/new entirely. May want to confirm with @Ferdi , though.
Image

@ChristopherChudzicki
Copy link
Copy Markdown
Contributor

@ahtesham-quraish One more note: I think this branch needs a rebase against your other branch, or against main after that is merged.

that should fix the failing openapi thing.

@ahtesham-quraish ahtesham-quraish force-pushed the ahtesham/tiptap-refactor branch from 5887609 to 44179d8 Compare May 20, 2026 06:50
Base automatically changed from ahtesham/tiptap-refactor to main May 20, 2026 09:18
Copilot AI review requested due to automatic review settings May 20, 2026 09:35
@ahtesham-quraish ahtesham-quraish force-pushed the ahtesham/tiptap-front-branch branch from 969c22a to d51df58 Compare May 20, 2026 09:38
@ahtesham-quraish ahtesham-quraish force-pushed the ahtesham/tiptap-front-branch branch from 8bb5ac7 to 7d25eb8 Compare May 20, 2026 09:42
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Refactors the Tiptap-based website content editor so the shared editor “shell” (save/publish UI, uploads, schema validation, toolbar plumbing, learning-resource drawer) can be reused across multiple website content types, while keeping the existing /news editor behavior and introducing a new /articles surface backed by the same API.

Changes:

  • Introduces a generic WebsiteContentEditor core and shared/base Tiptap extension factory, with per-content-type extension/config modules.
  • Adds content-type routed editor pages under /website_content/... and wires /news/* and /articles/* to those routes (mostly via redirects).
  • Extends backend website content plumbing for the new article type (e.g., CDN purge listing URL mapping) and adjusts Celery task naming for compatibility.

Reviewed changes

Copilot reviewed 42 out of 42 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
website_content/views.py Formatting-only cleanup in viewset.
website_content/models.py Formatting-only import spacing.
website_content/models_test.py Formatting-only import spacing.
website_content/migrations/0003_add_editors_group.py Formatting-only docstring spacing.
website_content/factories.py Formatting-only import spacing.
website_content/api.py Adds listing URL mapping for article content type during CDN purge.
website_content/api_test.py Formatting-only import spacing.
news_events/tasks.py Adds/adjusts Celery tasks/aliases for website-content-driven news ETL and backward compatibility.
news_events/etl/articles_news.py Minor formatting while syncing website content news to news feed.
frontends/main/src/page-components/TiptapEditor/useArticleSchema.ts Switches legacy “article schema” hook to use the new news content-type extensions (with deprecated alias exports).
frontends/main/src/page-components/TiptapEditor/TiptapEditor.tsx Updates viewer node mapping to allow configurable banner/byline viewers; adds a new styling hook.
frontends/main/src/page-components/TiptapEditor/index.ts Re-exports new generic editor + content-type editors (plus aliases for compatibility).
frontends/main/src/page-components/TiptapEditor/extensions/node/Banner/ArticleBannerNode.tsx Adds an article-specific banner node + viewer implementation.
frontends/main/src/page-components/TiptapEditor/extensions/node/ArticleByLineInfoBar/ArticleByLineInfoBarViewer.tsx Renames/export-aliases byline viewer and adds a banner-specific byline viewer.
frontends/main/src/page-components/TiptapEditor/extensions/node/ArticleByLineInfoBar/ArticleByLineInfoBarNode.ts Deprecates old node name and introduces ByLineInfoBarNode alias.
frontends/main/src/page-components/TiptapEditor/extensions/node/ArticleByLineInfoBar/ArticleByLineInfoBarInBanner.tsx Adds a simplified byline display component for the banner area.
frontends/main/src/page-components/TiptapEditor/extensions/node/ArticleByLineInfoBar/ArticleByLineInfoBar.tsx Renames exports (byline content/view) and updates context usage.
frontends/main/src/page-components/TiptapEditor/extensions/baseExtensions.ts Introduces shared “base” Tiptap extensions factory for all content types.
frontends/main/src/page-components/TiptapEditor/core/GenericEditor.tsx Adds the generic editor shell (WebsiteContentEditor) with save/publish UI, uploads, schema validation, and read-only viewer wiring.
frontends/main/src/page-components/TiptapEditor/contentTypes/news/newsExtensions.ts Adds news-specific document schema + extension factory and initial doc.
frontends/main/src/page-components/TiptapEditor/contentTypes/news/NewsEditor.tsx Adds news editor composition using the generic shell + news config and mutations.
frontends/main/src/page-components/TiptapEditor/contentTypes/article/articleExtensions.ts Adds article-specific document schema + extension factory and initial doc.
frontends/main/src/page-components/TiptapEditor/contentTypes/article/ArticleEditor.tsx Adds /articles editor composition using the generic shell + article config (including article banner viewer).
frontends/main/src/page-components/TiptapEditor/ArticleEditor.tsx Replaces the old monolithic editor with a backward-compatible re-export to the new news editor.
frontends/main/src/page-components/TiptapEditor/ArticleContext.tsx Generalizes article context to WebsiteContentContext with deprecated aliases.
frontends/main/src/common/urls.ts Adds URL helpers/constants for /articles and generic /website_content edit/create/drafts routes.
frontends/main/src/app/website_content/drafts/page.tsx Adds drafts route under /website_content/drafts.
frontends/main/src/app/website_content/[type]/new/page.tsx Adds content-type parameterized “new content” route.
frontends/main/src/app/website_content/[type]/[idOrSlug]/edit/page.tsx Adds content-type parameterized “edit content” route.
frontends/main/src/app/articles/page.tsx Adds /articles listing route.
frontends/main/src/app/articles/new/page.tsx Redirects /articles/new to the generic website content “new” route.
frontends/main/src/app/articles/draft/page.tsx Redirects /articles/draft to the generic drafts listing with content_type=article.
frontends/main/src/app/articles/[slugOrId]/page.tsx Adds /articles/:slugOrId detail route with SSR hydration + metadata generation.
frontends/main/src/app/articles/[slugOrId]/edit/page.tsx Redirects /articles/:slugOrId/edit to the generic website content editor route.
frontends/main/src/app/articles/[slugOrId]/draft/page.tsx Adds restricted draft view route for articles.
frontends/main/src/app/(site)/news/new/page.tsx Redirects legacy /news/new to the generic website content “new” route for news.
frontends/main/src/app/(site)/news/draft/page.tsx Redirects legacy /news/draft to the generic drafts listing with content_type=news.
frontends/main/src/app/(site)/news/[slugOrId]/edit/page.tsx Redirects legacy /news/:slugOrId/edit to the generic website content editor route.
frontends/main/src/app-pages/WebsiteContent/WebsiteContentNewPage.tsx Implements type-driven “new content” page that selects the correct editor composition.
frontends/main/src/app-pages/WebsiteContent/WebsiteContentEditPage.tsx Implements type-driven “edit content” page that loads content and selects the correct editor composition.
frontends/main/src/app-pages/WebsiteContent/WebsiteContentDraftListingPage.tsx Adds a unified drafts listing page intended to support filtering by content type.
frontends/main/src/app-pages/UserArticles/UserArticleNewPage.tsx Adds a user-article “new” page using the article editor composition.
frontends/main/src/app-pages/UserArticles/UserArticleListingPage.tsx Adds an /articles listing UI backed by useArticleList.
frontends/main/src/app-pages/UserArticles/UserArticleEditPage.tsx Adds an edit page for articles backed by useArticleDetailRetrieve.
frontends/main/src/app-pages/UserArticles/UserArticleDraftListingPage.tsx Adds a draft listing UI for articles backed by useArticleList({ draft: true }).
frontends/main/src/app-pages/UserArticles/UserArticleDetailPage.tsx Adds a read-only article detail renderer using the article editor composition.
Comments suppressed due to low confidence (1)

news_events/tasks.py:66

  • The module-level reassignment get_articles_news = get_website_content_news shadows the @app.task(name="news_events.tasks.get_articles_news") task defined just above. This is redundant and makes imports/calls to get_articles_news use the new task name (not the backward-compatible one). Prefer removing this reassignment and rely on the explicitly-named Celery task for backward compatibility.

content: JSONContent
extensions: Array<Extension | Node | Mark>
bannerViewer?: typeof BannerViewer
bylineViewer?: typeof BannerViewer
},
}
: {}),
"&& .tiptap.ProseMirror.abc > :nth-child(2)": {
article?: WebsiteContent
backgroundColor?: string
bannerViewer?: typeof BannerViewer
bylineViewer?: typeof BannerViewer
Comment on lines +191 to +199
const { create: createMutation, update: updateMutation } = saveMutations
const isPending = createMutation.isPending || updateMutation.isPending
const saveError = createMutation.error || updateMutation.error

const uploadImage = useMediaUpload()
// Keep a ref so the stable uploadHandler callback always calls the latest mutation.
const uploadImageRef = useRef(uploadImage)
uploadImageRef.current = uploadImage

Comment on lines +133 to +138
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const listParams: any = {
limit: PAGE_SIZE,
offset: (page - 1) * PAGE_SIZE,
draft: true,
...(contentType ? { content_type: contentType } : {}),
const [page, setPage] = useState(1)
const scrollRef = useRef<HTMLDivElement>(null)

const { data: articles, isLoading } = useArticleList({
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has been refactored and this file is removed

const { data: articles, isLoading: isLoadingArticles } = useArticleList({
limit: PAGE_SIZE,
offset: (page - 1) * PAGE_SIZE,
draft: true,
Comment on lines 36 to 38
addNodeView() {
// eslint-disable-next-line react-hooks/rules-of-hooks
return ReactNodeViewRenderer(ArticleByLineInfoBar)
Comment on lines 9 to 11
import { useWebsiteContent } from "../../../ArticleContext"
import { calculateReadTime } from "../../utils"
import SharePopover from "@/components/SharePopover/SharePopover"
@ahtesham-quraish ahtesham-quraish force-pushed the ahtesham/tiptap-front-branch branch from ec203c7 to 5496f48 Compare May 20, 2026 10:49
@ahtesham-quraish ahtesham-quraish force-pushed the ahtesham/tiptap-front-branch branch from ac90e20 to d55f623 Compare May 20, 2026 13:32
Copy link
Copy Markdown
Contributor

@ChristopherChudzicki ChristopherChudzicki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm getting lost lately with so many comments, so I'm going to put mine all in one comment, hopefully with enough context / links.


As discussed on zoom, still seeing a lot of naming issues. I know you're starting to address some of them already. Also a few other bugs.

Re Naming: We can't use article where we mean generic website content or news. article is specifically the content_type="article" from now one. This should apply everywhere. Otherwise, we end up with weird things like app-pages/Articles (which is really news) and app-pages/UserArticles which is really articles.

Naming

  1. Hooks: discussed on zoom; frontends/api/src/hooks/articles should be website_content now. I don't think they ever need to mention article or news explicitly.

  2. app-pages/Articles and **app-pages/UserArticles These are abot "news" and "article" content types, respectively, right? They should be named that way. Additionally, I think both of these directories should have much fewer files (See next item).

  3. Suggested File Structure: This PR has separate ArticleDetailPage and NewsDetailPage files (named differently, per Item 2 above). But they are nearly identical: They just render the editor in read-only mode. And that should continue into the future. Any visual difference in the pages should be handled by the tiptap rendering. So we should be able to get rid of both ArticleDetailPage and NewsDetailPage in favor of WebsiteContentDetail:

    app-pages/articles/
        ArticleListingPage.tsx
    app-pages/news/
        NewsListingPage.tsx
    app-pages/website_content/
        WebsiteContentDetail.tsx                (suggested as new, to replace
                                                ArticleDetailPage, etc)
        WebsiteContentDraftListingPage.ts       (exists)
        WebsiteContentEditPage.tsx              (exists)
        WebsiteContentNewPage.tsx               (exists)
        + test files
    

    IMO Makes sense to keep the two separate route directories app/news/... and app/articles..., though.

  4. Dead Code: ArticleNewPage, ArticleEditPage, UserArticleEditPage, UserArticleNewPage ... aren't these all dead code now that WebsiteContentEditPage etc exist?

    • ⚠️ ArticleNewPage.test.tsx (existing) should probably be converted into WebsiteContentNewPage.test.tsx before deleting the former.
  5. urls.ts: This includes things like ARTICLES_LISTING=/news. Could you double check this file?

  6. TiptapEditor/index.ts:2 exports ArticleEditor as UserArticleEditor but nothing uses this. Let's delete the export.

  7. Styling Comment: Copilot's comment #3344 (comment) is accurate and worth fixing; besides the fragile selector, should replace abc with a useful class name.

  8. ⚠️⚠️ Can't create articles: When try creating a new article at http://open.odl.local:8062/website_content/article/new, the content_type is "news".

  9. Listing Page Filter: See #3344 (comment)

  10. byLineViewer type: See #3344 (comment)

  11. Remove Dead Props: ArticleEditorProps in TiptapEditor/contentTypes/article/ArticleEditor.tsx marks several things as deprecated, but they are never used. Just delete them. Same for /contentTypes/news/NewsEditor.tsx

  12. Remove Deprecated ArticleByLineInBannerViewer: At https://github.com/mitodl/mit-learn/pull/3344/changes#diff-189cba8943939cc1aa7937abb3d5f8f9e592f1777749d828f954f2abe05e0c04R43 There's only one callsite, just remove the deprecated thing.

  13. Permission.ArticleEditor: Either rename this and update the backend serializer, or if you want to keep backend untouched for this PR, at least leave a comment that the permission is actually for all website_content; I'd be ok with teither for now.

Note

Would suggest doing a few copilot local reviews making sure everything is addressed... a few of Copilot's relevant comments were not addressed, so a local copilot review helps make sure.

@ahtesham-quraish ahtesham-quraish force-pushed the ahtesham/tiptap-front-branch branch from 8188ed6 to 454936e Compare May 21, 2026 07:55
@ahtesham-quraish ahtesham-quraish added Needs Review An open Pull Request that is ready for review and removed Waiting on author labels May 21, 2026
@ChristopherChudzicki
Copy link
Copy Markdown
Contributor

ChristopherChudzicki commented May 21, 2026

  1. Styling Comment: Copilot's comment refactor: make internal editor code more generic #3344 (comment) is accurate and worth fixing; besides the fragile selector, should replace abc with a useful class name.
  2. ... fixed ✅
  3. ... fixed ✅
  4. byLineViewer type: See refactor: make internal editor code more generic #3344 (comment)

I don't think these got addressed.

I had copilot run a quick review locally, and it caught some more things:

  • Lingering UserArticleCard in ArticleListingPage, plus USER_ARTICLES_* constants in urls.ts`.
    • note: These can just be article now, right? You renamed the directory, but still using "User Article", which isn't a great name since most users can't create these things.
  • common/articleUtils.ts still caries article name, but it's website_content agnostic now
  • ArticleViewer.test.tsx file + describe("ArticleViewer") block still article-named while testing NewsEditor.

@ahtesham-quraish ahtesham-quraish force-pushed the ahtesham/tiptap-front-branch branch 3 times, most recently from f95700b to ea23f51 Compare May 21, 2026 17:34
@ahtesham-quraish ahtesham-quraish force-pushed the ahtesham/tiptap-front-branch branch 2 times, most recently from 405ed1e to bc6a4ac Compare May 21, 2026 17:53
@ahtesham-quraish ahtesham-quraish force-pushed the ahtesham/tiptap-front-branch branch 3 times, most recently from 5fab43c to c49de33 Compare May 22, 2026 06:47
@ahtesham-quraish ahtesham-quraish force-pushed the ahtesham/tiptap-front-branch branch from 1c9e448 to fe59476 Compare May 22, 2026 12:45
Copy link
Copy Markdown
Contributor

@ChristopherChudzicki ChristopherChudzicki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking much cleaner. Some comments below, including one styling regression for published news articles.

There is also some complication around the banner / byline difference sbetween news + article that I hadn't appreciated, and which I'll comment about in a followup.

  1. http://open.odl.local:8062/website_content/drafts/ with no query param seems to show "Articles" title but display all website content.
    • I think the simplest thing for now would be to make the default behave like content_type=news. What do you think?
  2. A few lingering renames
    • WebsiteContentContext still has its field named article?: WebsiteContent, and the core WebsiteContentEditor prop is still article
      (NewsEditor renamed its own prop but passes article={newsItem}).
    • Some lingering UserArticle references (in comments)
    • WebsiteContentDetail uses articleId where it should use contentId. Some internal article naming to WebsiteContentDetail, see https://github.com/mitodl/mit-learn/pull/3344/changes#r3289665347
  3. /articles/ should be read-only, like "news", right?
    • ⚠️ Per discussion in slack, I lean toward just getting rid of this route for now, until we get designs from Bilal.
  4. https://github.com/mitodl/mit-learn/pull/3344/changes#r3289681768
  5. Metadata... Let's share extractImageMetadata + add a content_type check https://github.com/mitodl/mit-learn/pull/3344/changes#r3289724607
  6. ⚠️ Styling regression vs main, see https://github.com/mitodl/mit-learn/pull/3344/changes#r3290090895
  7. Banner breadcrumb text, see https://github.com/mitodl/mit-learn/pull/3344/changes#r3290389595
  8. Please see #3344 (comment).

New Routes

The new routes make sense to me, but I think we should (A) remove /articles/new, /articles/draft, and /articles/[slugOrId]/edit and (B) double check with Ferdi if we actually want /articles/ route at all yet / if it should be feature flagged.

/website_content/[type]/new
/website_content/[type]/[idOrSlug]/edit
/website_content/drafts?content_type=...

NEW ARTICLE ROUTES:
PUBLIC
    /articles                     <-- Let's check if we want this
    /articles/[slugOrId]
PRIVATE:
    /articles/[slugOrId]/draft
    /articles/new                 <-- suggest removing
    /articles/draft               <-- suggest removing
    /articles/[slugOrId]/edit     <-- suggest removing

EXISTING NEWS ROUTES
PUBLIC
    /news
    /news/[slugOrId]
PRIVATE
/news/[slugOrId]/draft
    /news/new                 <-- unnecessary, but redirects for backward compat
    /news/draft               <-- unnecessary, but redirects for backward compat
    /news/[slugOrId]/edit     <-- unnecessary, but redirects for backward compat

learningResourceIds?: number[]
}) => {
const { data: article, isLoading } = useArticleDetailRetrieve(articleId)
const { data: article, isLoading } =
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be content, right? Not article. Same for the prop articleId above... should be contentId.

Comment on lines +48 to +52
{article.content_type === "article" ? (
<ArticleEditor article={article} readOnly />
) : (
<NewsEditor newsItem={article} readOnly />
)}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will be adding more content types, so a dictionary lookup would be more scalable here than conditionals.

Alternatively, rename WebsiteContentEditor to BaseWebsiteContentEditor and have WebsiteContentEditor itself switch based on content_type.

)

const description = extractArticleDescription(article)
const leadImage = extractImageMetadata(article)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extractImageMetadata is copied between both article/ and /news pages. I think we should put this in @/common/website_content.ts. You could do the same for extractArticleDescription, though that more conceivably differs between content types (even though it does not right now).

Additionally, I'd suggest renaming article to content, and adding a check

if (content.content_type != "article") notFound()

and a similar check on the news page.

Comment on lines +60 to +74
const toolbarSlot = readOnly ? (
<>
<Spacer />
<ButtonLink
variant="secondary"
href="/website_content/drafts?content_type=article"
size="small"
>
Drafts
</ButtonLink>
<ButtonLink variant="primary" href={editUrl} size="small">
Edit
</ButtonLink>
</>
) : null
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

toolbarSlot is a function purely of content_type.

If you give WebsiteContentEditor a content_type: WebsiteContentContentTypeEnum prop, you could construct it there very easily rather than having both ArticleEditor and NewsEditor construct it.

  • "Drafts" link → websiteContentDraftsView(contentType)
  • "Edit" link → websiteContentEditView(contentType, slugOrId)

Comment on lines +85 to +87
"&& .tiptap.ProseMirror.tiptap-viewer > :nth-child(2)": {
paddingTop: "36px",
},
Copy link
Copy Markdown
Contributor

@ChristopherChudzicki ChristopherChudzicki May 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ This is a visual regression on News pages:

Image

IMO:

  • if we actually need this, it should be scoped to articles only.
  • But maybe we can just remove it ? (Do we have designs for this page?)

variant="dark"
ancestors={[
{ href: "/", label: "Home" },
{ href: "/articles", label: "MIT Learn Articles" },
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just saw discussion in slack... This should be just "Articles" not "MIT Learn Articles"

@ChristopherChudzicki
Copy link
Copy Markdown
Contributor

@ahtesham-quraish I took a closer look at the banner and byline nodes today. Details below, along with an example commit I think you should apply to this branch. (But please read this and the commit carefully to see if you agree.)


The byline is more different than it needs to be between article and news: News has a single render path for the byline, article has two, which leads to some bugs. More broadly, the article editor has differences between read-only and editing mode that it should not have.

Here's a more detailed summary:

  • News:
    • banner node renders BannerWrapper
    • byline node renders ByLineInfoBar (which includes metadata from the article as a whole)
  • Articles: For articles, the behavior is divergent between editing mode and read-only mode:
    • The banner node renders as:
      • Editing mode: ArticleBannerWrapper
      • read-only mode: a differently-implemented ArticleBannerViewer, which includes the byline
    • The byline node renders as:
      • Editing Mode: ByLineInfoBar
      • Read-only: Null

This leads to divergence between the editing and read-only views for articles. Concretely:

  1. visual differences (breadcrumbs are different + byline is different)
  2. functional differences (the ArticleBannerViewer didn't implement the share button)

We should unify the two views:

  • Both editing and read-only should render ArticleBannerWrapper
    • technically editing vs viewing still wraps differently (ReactNodeViewRenderer vs ReactNodeViewContentProvider wrapper) but our custom UI is the same.
  • Both editing and read-only render ByLineInfoBar, same for news vs article
    • account for different styles by CSS

I took this approach in 35f6bf9 (branch ahtesham/tiptap-front-branch-cc-suggestions)

Styling

You'll see now "news" and "article" use exactly the same ByLineInfoBar node. They are just styled slightly differently via a classname on WebsiteContentEditor. In general, where UI differs between article vs news vs other content types but is structurally the same (author - reading time - date - share) we should account for the differences just with CSS. (Aside: I would say that if they are structurally very similar, but slightly different, then we should at least raise with designers (Bilal) whether they can be made structurally identical.)

Additionally, you'll see in the commit above that all styling differences for ByLine between article and news live in ArticleEditor.tsx. This is better than putting them at the node level...As we add more content types, individual nodes should not need to know about the content type, at least if the differences are style.

Note: I also dropped the backgroundColor prop on WebsiteContentEditor in the commit above. No need for a prop, can just be css via styled(...).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Needs Review An open Pull Request that is ready for review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants