Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions article/migrations/0049_tiptaparticlepage.py

Large diffs are not rendered by default.

18 changes: 18 additions & 0 deletions article/migrations/0050_tiptaparticlepage_body_to_html.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2 on 2026-03-08 08:25

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('article', '0049_tiptaparticlepage'),
]

operations = [
migrations.AlterField(
model_name='tiptaparticlepage',
name='body',
field=models.TextField(blank=True, default=''),
),
]
17 changes: 17 additions & 0 deletions article/migrations/0051_remove_tiptap_lede.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Generated by Django 4.2 on 2026-03-09 02:09

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('article', '0050_tiptaparticlepage_body_to_html'),
]

operations = [
migrations.RemoveField(
model_name='tiptaparticlepage',
name='lede',
),
]
38 changes: 37 additions & 1 deletion article/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@

from wagtailmenus.models import FlatMenu


from article.widgets import TipTapAdminWidget
from wagtail_color_panel.fields import ColorField
from wagtail_color_panel.edit_handlers import NativeColorPanel

Expand Down Expand Up @@ -1812,6 +1812,42 @@ class Meta:
verbose_name_plural = "Standard Articles"


class TipTapArticlePage(Page):
"""
A rich-text article page using TipTap as the body editor.
Body is stored as HTML produced by TipTap's getHTML().
The public page renders it directly — no client-side JS needed.

Editing uses the standard Wagtail page editor, giving automatic support
for revision history, draft/publish workflow, editorial comments, and
page locking.
"""
body = models.TextField(blank=True, default='')

content_panels = Page.content_panels + [
FieldPanel('body', widget=TipTapAdminWidget()),
]

edit_handler = TabbedInterface([
ObjectList(content_panels, heading='Content'),
ObjectList(Page.promote_panels, heading='Promote'),
ObjectList(Page.settings_panels, heading='Settings'),
])

parent_page_types = [
'specialfeaturelanding.SpecialLandingPage',
'section.SectionPage',
]
subpage_types = []

def get_template(self, request, *args, **kwargs):
return 'article/tiptap_article_page.html'

class Meta:
verbose_name = "TipTap Article Page"
verbose_name_plural = "TipTap Article Pages"


class SpecialArticleLikePage(ArticlePage):

show_in_menus_default = True
Expand Down
23 changes: 23 additions & 0 deletions article/templates/article/tiptap_article_page.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{% extends 'ubyssey/base.html' %}
{% load static %}
{% load wagtailcore_tags %}

{% block stylesheet %}
{{ block.super }}
<link rel="stylesheet" href="{% static 'ubyssey/css/tiptap-article.css' %}" type="text/css" />
{% endblock %}

{% block header %}
{% include 'navigation/headers/topbar.html' %}
{% include 'navigation/headers/mobile.html' %}
{% endblock %}

{% block content %}
<main class="tiptap-article-page">
<div class="tiptap-article">
<div class="tiptap-article__body">
{{ page.body|safe }}
</div>
</div>
</main>
{% endblock %}
7 changes: 2 additions & 5 deletions article/views.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
from django.shortcuts import render

# Create your views here.
from wagtail.admin.panels import FieldPanel, ObjectList, TabbedInterface
from wagtail.admin.ui.tables import UpdatedAtColumn
from wagtail.snippets.models import register_snippet
from wagtail.snippets.views.snippets import SnippetViewSet

from article.models import ArticleTopic


class ArticleTopicViewSet(SnippetViewSet):
model = ArticleTopic
icon = "pick"
Expand All @@ -18,4 +15,4 @@ class ArticleTopicViewSet(SnippetViewSet):
search_fields = ["name"]
ordering = "-last_used_at"

list_export = ["name", "tagged_articles_count", "most_frequent_section", "last_used_at"]
list_export = ["name", "tagged_articles_count", "most_frequent_section", "last_used_at"]
23 changes: 23 additions & 0 deletions article/widgets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from django import forms


class TipTapAdminWidget(forms.Textarea):
"""
A Textarea whose value is TipTap HTML. The textarea is hidden with CSS;
a TipTap editor is mounted next to it by tiptap-wagtail-widget.jsx.
Wagtail's form submission reads the (JS-kept-in-sync) textarea value
and Django saves it to the body TextField directly.
"""

def __init__(self, *args, **kwargs):
attrs = kwargs.setdefault('attrs', {})
attrs['class'] = (attrs.get('class', '') + ' js-tiptap-admin-field').strip()
attrs['style'] = 'display: none'
super().__init__(*args, **kwargs)

def format_value(self, value):
return value or ''

class Media:
js = ['ubyssey/js/tiptap-wagtail-widget.js']
css = {'all': ['ubyssey/css/tiptap-article.css']}
2 changes: 1 addition & 1 deletion config/settings/development.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# Two Scoops of Django, p. 47: "For the singular case of Django setting modules we want to override all the namespace"
# Therefore the below "import *" is correct
from .base import *

DEBUG = True
WAGTAILADMIN_BASE_URL = 'http://localhost:8000/'

ALLOWED_HOSTS = ['localhost', '*']
Expand Down
2 changes: 1 addition & 1 deletion mount
Submodule mount updated from b46df8 to 708469
1 change: 1 addition & 0 deletions section/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ class SectionPage(RoutablePageMixin, SectionablePage):
template = 'section/section_page.html'

subpage_types = [
'article.TipTapArticlePage',
'article.StandardArticlePage',
'article.StandardArticlePageWithRightColumn',
'article.SpecialArticleLikePage',
Expand Down
1 change: 1 addition & 0 deletions specialfeaturelanding/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ def get_template(self, request):
]

subpage_types = [
'article.TipTapArticlePage',
'specialfeaturelanding.SpecialLandingPage',
'article.ArticlePage',
]
Expand Down
Loading
Loading