Skip to content

Commit

Permalink
Merge pull request #98 from happychuks/main
Browse files Browse the repository at this point in the history
feat: Migration from CKEditor5 to TinyMCE
  • Loading branch information
happychuks authored Aug 25, 2024
2 parents 4b72e2d + e2a9a5f commit 996e73d
Show file tree
Hide file tree
Showing 13 changed files with 203 additions and 128 deletions.
200 changes: 93 additions & 107 deletions research/src/layouts/Footer.astro
Original file line number Diff line number Diff line change
Expand Up @@ -107,126 +107,112 @@
</div>
</div>

<div class="flex items-center justify-center text-white" id="subscribe">
<div class="text-center flex flex-col justify-center gap-4 w-max">
<div>
<h3 class="text-3xl font-semibold">Your Ethereum Edge</h3>
<p class="text-white">
Get first-hand research delivered by our team of experts.
</p>
</div>
<form
action="https://cms.2077.xyz/newsletter/subscribe/"
method="post"
id="subscribe-form"
class="flex justify-center"
>
<!-- CSRF token will be added dynamically -->
<input
type="email"
name="email"
placeholder="Enter your email"
required
class="outline-none rounded-l bg-white border border-black w-max-content p-2 text-sm w-full border-r-none text-black"
/>
<button
type="submit"
class="rounded-r bg-[#91b208] px-4 py-2 font-bold min-w-[110px] min-h-[42px] text-white"
>
Subscribe
</button>
</form>
<span id="response-message" class="text-sm"></span>
<div class="flex items-center justify-center text-white" id="subscribe">
<div class="text-center flex flex-col justify-center gap-4 w-max">
<div>
<h3 class="text-3xl font-semibold">Your Ethereum Edge</h3>
<p class="text-white">
Get first-hand research delivered by our team of experts.
</p>
</div>

<form
action="https://cms.2077.xyz/newsletter/subscribe/"
method="post"
id="subscribe-form"
class="flex justify-center"
>
<!-- CSRF token will be added dynamically -->
<input
type="email"
name="email"
placeholder="Enter your email"
required
class="outline-none rounded-l bg-white border border-black w-max-content p-2 text-sm w-full border-r-none text-black"
/>
<button
type="submit"
class="rounded-r bg-[#91b208] px-4 py-2 font-bold min-w-[110px] min-h-[42px] text-white"
>
Subscribe
</button>
</form>
<span id="response-message" class="text-sm"></span>
</div>

<script>
document.addEventListener('DOMContentLoaded', () => {
// TODO: this should be moved to another comp. along with the email form
const form = document.getElementById('subscribe-form') as HTMLFormElement | null

if (form) {
// Fetch CSRF token
fetch('https://cms.2077.xyz/get-csrf-token/', {
credentials: 'include', // Ensure cookies are sent with the request
})
.then((response) => response.json())
.then((data) => {
const csrfToken = data.csrfToken
if (form) {
// Fetch CSRF token
fetch('https://cms.2077.xyz/get-csrf-token/', {
credentials: 'include', // Ensure cookies are sent with the request
})
.then((response) => response.json())
.then((data) => {
const csrfToken = data.csrfToken;

// Add CSRF token as a hidden input in the form
const input = document.createElement('input');
input.type = 'hidden';
input.name = 'csrfmiddlewaretoken';
input.value = csrfToken;
form.appendChild(input);

// Add CSRF token as a hidden input in the form
const input = document.createElement('input')
input.type = 'hidden'
input.name = 'csrfmiddlewaretoken'
input.value = csrfToken
form.appendChild(input)
// Handle form submission
form.addEventListener('submit', async (event) => {
event.preventDefault();

// Handle form submission
form.addEventListener('submit', (event) => {
event.preventDefault()
const formData = new FormData(form);

const formData = new FormData(form)
try {
const response = await fetch(form.action, {
method: 'POST',
body: formData,
headers: {
'X-CSRFToken': csrfToken,
},
credentials: 'include', // Ensure cookies are sent with the request
});

fetch(form.action, {
method: 'POST',
body: formData,
headers: {
'X-CSRFToken': csrfToken,
},
credentials: 'include', // Ensure cookies are sent with the request
})
.then((response) => {
if (response.ok) {
return response.json()
} else {
return response
.json()
.then((data) => {
throw new Error(
data.message ||
'An error occurred',
)
})
}
})
.then(() => {
const messageElement =
document.getElementById(
'response-message',
)
if (messageElement) {
messageElement.textContent =
'Subscription successful!'
messageElement.style.color = 'green'
}
})
.catch((error) => {
const messageElement =
document.getElementById(
'response-message',
)
if (messageElement) {
messageElement.textContent =
error.message ||
'An error occurred. Please try again.'
messageElement.style.color = 'red'
}
})
})
})
.catch((error) => {
console.error('Error fetching CSRF token:', error)
const messageElement =
document.getElementById('response-message')
const messageElement = document.getElementById('response-message');
if (response.ok) {
if (messageElement) {
messageElement.textContent = 'Subscription successful!';
messageElement.style.color = 'green';
}
} else {
const data = await response.json();
throw new Error(data.message || 'An error occurred');
}
} catch (error) {
const messageElement = document.getElementById('response-message');
if (messageElement) {
messageElement.textContent =
'An error occurred while fetching the CSRF token. Please try again.'
messageElement.style.color = 'red'
(error instanceof Error
? error.message
: 'An error occurred. Please try again.');
messageElement.style.color = 'red';
}
})
}
})
</script>
</div>
}
});
})
.catch((error) => {
console.error('Error fetching CSRF token:', error);
const messageElement = document.getElementById('response-message');
if (messageElement) {
messageElement.textContent = 'An error occurred while fetching the CSRF token. Please try again.';
messageElement.style.color = 'red';
}
});
}
});
</script>
</div>




<div class="grid mt-16 flex justify-center">
<a href="/" class="logo">
Expand Down
10 changes: 4 additions & 6 deletions server/apps/research/admin/article_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@
from django import forms
from django_ckeditor_5.widgets import CKEditor5Widget
from apps.research.models import Article, Author
from tinymce.widgets import TinyMCE

@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
"""Admin interface for the Article model."""
class Media:
# Reference the JavaScript file
js = ('ckeditor/js/custom_ckeditor_admin.js',)
fieldsets = [
('Article Details', {'fields': ['title', 'authors', 'categories', 'thumb', 'content', 'summary', 'status', 'scheduled_publish_time']}),
('Article Details', {'fields': ['title', 'authors', 'acknowledgement', 'categories', 'thumb', 'content', 'summary', 'status', 'scheduled_publish_time']}),
]
list_display = ('title', 'display_authors', 'status', 'views', 'display_categories', 'created_at', 'scheduled_publish_time')
search_fields = ('title', 'authors__user__username', 'authors__twitter_username', 'content')
Expand All @@ -20,10 +18,10 @@ class Media:
list_editable = ('status',)

def get_form(self, request, obj=None, **kwargs):
"""Return a form with CKEditor5Widget for the content field."""
"""Return a form with TinyMCE Widget for the content field."""
form = super().get_form(request, obj, **kwargs)
if 'content' in form.base_fields:
form.base_fields['content'].widget = CKEditor5Widget(attrs={"class": "django_ckeditor_5"}, config_name='extends')
form.base_fields['content'].widget = TinyMCE(attrs={'cols': 80, 'rows': 30, 'id': "richtext_field"})
return form

def display_authors(self, obj):
Expand Down
4 changes: 2 additions & 2 deletions server/apps/research/admin/author_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ class AuthorAdmin(admin.ModelAdmin):
"""Admin interface for the Author model."""

fieldsets = [
('Author Details', {'fields': ['user', 'bio', 'twitter_username']}),
('Author Details', {'fields': ['user', 'full_name', 'bio', 'twitter_username']}),
]
list_display = ('user', 'bio', 'twitter_username')
list_display = ('user', 'full_name', 'bio', 'twitter_username')
list_per_page = 25
search_fields = ('user__username', 'twitter_username')

Expand Down
20 changes: 15 additions & 5 deletions server/apps/research/models/article.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from .author import Author
from django.utils import timezone
from django.conf import settings
from tinymce.models import HTMLField


def get_default_thumb():
Expand All @@ -21,8 +22,10 @@ class Article(BaseModel):
)

title = models.TextField()
content = CKEditor5Field('Text', null=True, blank=True, config_name='extends')
#content = CKEditor5Field('Text', null=True, blank=True, config_name='extends')
content = HTMLField(blank=True, null=True)
summary = models.TextField(blank=True)
acknowledgement = models.TextField(blank=True, null=True)
authors = models.ManyToManyField(Author, blank=True, related_name='articles')
slug = models.SlugField(blank=True)
categories = models.ManyToManyField(Category, blank=True, related_name='articles')
Expand All @@ -35,19 +38,19 @@ class Article(BaseModel):
post_objects = ArticleObjects()

class Meta:
ordering = ('-created_at',)
ordering = ('-scheduled_publish_time',)

def __str__(self):
return self.title

def save(self, *args, **kwargs):
"""Override the save method to generate a unique slug."""
if not self.slug:
"""Override save method to handle slug and scheduled publishing."""
if not self.slug or self.title_update():
self.slug = self.generate_unique_slug()

"""Override the save method to handle scheduled publishing."""
if self.scheduled_publish_time and self.status == 'draft' and timezone.now() >= self.scheduled_publish_time:
self.status = 'ready'

super().save(*args, **kwargs)

def generate_unique_slug(self):
Expand All @@ -59,3 +62,10 @@ def generate_unique_slug(self):
slug = f"{base_slug}-{num}"
num += 1
return slug

def title_update(self):
"""Check if the title has changed."""
if self.pk:
original = Article.objects.get(pk=self.pk)
return original.title != self.title
return False
1 change: 1 addition & 0 deletions server/apps/research/models/author.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class Author(BaseModel):
user = models.OneToOneField(User, on_delete=models.CASCADE)
bio = models.TextField(blank=True, null=True)
twitter_username = models.CharField(max_length=100, blank=True, null=True)
full_name = models.CharField(max_length=255, blank=True, null=True)

class Meta:
verbose_name_plural = 'Authors'
Expand Down
4 changes: 2 additions & 2 deletions server/apps/research/serializers/article_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class Meta:
model = Article
fields = [
'id', 'slug', 'title', 'authors', 'thumb',
'categories', 'summary', 'content',
'categories', 'summary', 'acknowledgement', 'content',
'status', 'views', 'created_at', 'updated_at', 'scheduled_publish_time'
]

Expand All @@ -25,7 +25,7 @@ class ArticleCreateUpdateSerializer(serializers.ModelSerializer):

class Meta:
model = Article
fields = ['title', 'categories', 'thumb', 'content', 'summary', 'status', 'authors', 'scheduled_publish_time']
fields = ['title', 'categories', 'thumb', 'content', 'summary', 'acknowledgement', 'status', 'authors', 'scheduled_publish_time']

# TODO : Debug this method to add the logged-in user as the author when creating a new article
def create(self, validated_data):
Expand Down
2 changes: 1 addition & 1 deletion server/apps/research/serializers/author_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class AuthorSerializer(serializers.ModelSerializer):

class Meta:
model = Author
fields = ['id', 'username', 'bio', 'twitter_username']
fields = ['id', 'username', 'full_name', 'bio', 'twitter_username']

def get_username(self, obj):
return obj.user.username
Loading

0 comments on commit 996e73d

Please sign in to comment.