Skip to content
Merged
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
uploads/

#Special gitignore for Django
staticfiles/
migrations/
Expand Down
24 changes: 17 additions & 7 deletions forum/markdown.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
from django.db import models
from markdown_it import MarkdownIt

from mdeditor.fields import MDTextField
import markdown
from md_editor.models import MDTextField
import bleach

def highlight_code(code, lang, attrs):
return (
'<pre><code class="language-{}">{}</code></pre>'
.format(
lang,
code
)
)

md = MarkdownIt("commonmark", {"linkify": True, "highlight": highlight_code})

class MarkdownModel(models.Model):
content = MDTextField(max_length=40000)
content_html = models.TextField(editable=False, blank=True)
Expand All @@ -20,13 +31,14 @@ class MarkdownModel(models.Model):
"blockquote","b", "i", "strong", "em", "a", "p", "ul", "ol", "li",
"code", "pre", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "img",
"table", "thead", "tr", "th", "tbody", "td", "sup", "dt", "dd", "dl",
"abbr", "div", "br"
"abbr", "div", "span", "br"
]

allowed_attrs = {
"a": ["href", "title"],
"img": ["src", "alt", "title"],
"div" : ["class"]
"div": ["class"],
"span": ["class"]
}

ALLOWED_PROTOCOLS = ["http", "https", "mailto"]
Expand All @@ -35,9 +47,7 @@ class Meta:
abstract = True

def markdown_render(self, content):
content_html = markdown.markdown(
content, extensions=self.MARKDOWN_EXTENSIONS
)
content_html = md.render(content)
content_html = bleach.clean(content_html, tags=self.allowed_tags,
attributes=self.allowed_attrs,
protocols=self.ALLOWED_PROTOCOLS)
Expand Down
184 changes: 136 additions & 48 deletions forum/templates/base.html
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<!DOCTYPE html>
{% load static %}
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Lean Forum{% endblock %}</title>

<meta name="color-scheme" content="light dark">
<meta name="color-scheme" content="light dark">
<script>
function getTheme() {
const saved = localStorage.getItem('theme');
Expand All @@ -14,13 +14,37 @@
}
document.documentElement.setAttribute('data-bs-theme', getTheme());
</script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.8.1/github-markdown.css" integrity="sha512-Hasfm7Iv5AG2/v5DSRXetpC33VjyPBXn5giooMag2EgSbiJ2Xp4GGvYGKSvc68SiJIflF/WrbDFdNmtlZHE5HA==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<link
id="github-markdown-light"
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.9.0/github-markdown-light.min.css"
referrerpolicy="no-referrer"
/>
<link
id="github-markdown-dark"
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.9.0/github-markdown-dark.min.css"
referrerpolicy="no-referrer"
disabled
/>

<link id="highlightjs-light" rel="stylesheet" href="https://unpkg.com/@highlightjs/cdn-assets@11.11.1/styles/default.min.css">
<link id="highlightjs-dark" rel="stylesheet" href="https://unpkg.com/@highlightjs/cdn-assets@11.11.1/styles/github-dark.min.css" disabled>
<script src="https://unpkg.com/@highlightjs/cdn-assets@11.11.1/highlight.min.js"></script>

<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@4/tex-mml-chtml.js"></script>

<link href="https://cdn.jsdelivr.net.cn/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">

<style>
:root {
color-scheme: light dark;
}

body {
transition: background-color 0.2s ease, color 0.2s ease;
}

.markdown-body {
box-sizing: border-box;
min-width: 200px;
Expand All @@ -29,26 +53,49 @@
padding: 45px;
}

.page-shell {
min-height: calc(100vh - 8rem);
}

.back-to-top {
bottom: 30px;
right: 30px;
z-index: 99;
width: 44px;
height: 44px;
opacity: 0.9;
transition: opacity 0.2s ease, transform 0.2s ease;
}

.back-to-top:hover {
transform: translateY(-2px);
opacity: 1;
}

@media (max-width: 767px) {
.markdown-body {
padding: 15px;
}
}

[data-bs-theme="dark"] body {
background-color: #0d1117;
}

[data-bs-theme="dark"] body {
background-color: #0d1117;
}
[data-bs-theme="dark"] .markdown-body pre,
[data-bs-theme="dark"] .markdown-body code {
background: #0d1117 !important;
color: #c9d1d9 !important;
}
</style>
{% block extra_head %}{% endblock %}
</head>
{% load static %}
<body class="d-flex flex-column min-vh-100">
<header>
<nav class="navbar navbar-expand-lg">
<div class="container-fluid">
<a class="navbar-brand" href="{% url 'index' %}">
<span>
<header class="border-bottom shadow-sm">
<nav class="navbar navbar-expand-lg navbar-light bg-body-tertiary py-2">
<div class="container-fluid px-2 px-lg-3">
<a class="navbar-brand d-flex align-items-center gap-2 fw-semibold" href="{% url 'index' %}">
<span class="d-inline-flex align-items-center">
{% include 'forum/icons/chat-left.svg' %}
</span>
<span>Lean Forum</span>
Expand All @@ -57,7 +104,7 @@
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link active" href="{% url 'index' %}">首页</a>
</li>
Expand All @@ -81,14 +128,14 @@
</ul>
</div>

<div class="d-flex align-items-center ms-auto">
<a class="nav-link px-2" href="#" id="theme-toggle" title="切换主题">
<div class="d-flex align-items-center gap-2 ms-auto">
<a class="btn btn-link p-2" href="#" id="theme-toggle" title="切换主题" aria-label="切换主题">
<span id="theme-icon"></span>
</a>
{% if user.is_authenticated %}
<div class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<span>
<a class="nav-link dropdown-toggle d-flex align-items-center gap-2" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<span class="d-inline-flex align-items-center">
{% include 'forum/icons/person-circle.svg' %}
</span>
<span>{{ user.username }}</span>
Expand All @@ -111,18 +158,18 @@
</nav>
</header>

<main class="container mt-4">
<main class="container-lg container-fluid py-4 page-shell">
{% block content %}{% endblock %}
</main>

<footer class="text-center text-lg-start mt-auto">
<div class="text-center p-3">
<footer class="mt-auto border-top">
<div class="text-center py-3 text-muted">
&copy; 2026 Lean Forum
</div>
</footer>

<button id="back-to-top" title="返回顶部" class="btn position-fixed rounded-circle border d-flex align-items-center justify-content-center text-body opacity-75" style="display:none;bottom:30px;right:30px;z-index:99;width:40px;height:40px;transition:opacity .3s">
<span>
<button id="back-to-top" title="返回顶部" class="btn btn-outline-secondary rounded-circle position-fixed back-to-top d-none d-flex align-items-center justify-content-center text-body">
<span class="d-inline-flex align-items-center">
{% include 'forum/icons/arrow-up.svg' %}
</span>
</button>
Expand All @@ -131,34 +178,75 @@


<script>
function updateThemeIcon() {
const icon = document.getElementById('theme-icon');
const isDark = document.documentElement.getAttribute('data-bs-theme') === 'dark';
icon.innerHTML = isDark
? `{% include 'forum/icons/moon.svg' %}`
: `{% include 'forum/icons/sun.svg' %}`;
}
updateThemeIcon();

document.getElementById('theme-toggle').addEventListener('click', (e) => {
e.preventDefault();
const isDark = document.documentElement.getAttribute('data-bs-theme') === 'dark';
const next = isDark ? 'light' : 'dark';
localStorage.setItem('theme', next);
document.documentElement.setAttribute('data-bs-theme', next);
updateThemeIcon();
});

window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
if (!localStorage.getItem('theme')) {
document.documentElement.setAttribute('data-bs-theme', getTheme());
updateThemeIcon();
const themeToggle = document.getElementById('theme-toggle');
const themeIcon = document.getElementById('theme-icon');
const markdownLight = document.getElementById('github-markdown-light');
const markdownDark = document.getElementById('github-markdown-dark');
const prefersDarkMedia = window.matchMedia('(prefers-color-scheme: dark)');
const btn = document.getElementById('back-to-top');

function updateThemeIcon(isDark) {
if (themeIcon) {
themeIcon.innerHTML = isDark
? `{% include 'forum/icons/moon.svg' %}`
: `{% include 'forum/icons/sun.svg' %}`;
}
});
}

const btn = document.getElementById('back-to-top');
window.addEventListener('scroll', () => btn.style.display = window.scrollY > 300 ? 'block' : 'none');
btn.addEventListener('click', () => window.scrollTo({top:0,behavior:'smooth'}));
function updateMarkdownTheme(isDark) {
if (markdownLight && markdownDark) {
markdownLight.disabled = isDark;
markdownDark.disabled = !isDark;
}
}

function updateHighlightTheme(isDark) {
const highlightLight = document.getElementById('highlightjs-light');
const highlightDark = document.getElementById('highlightjs-dark');
if (highlightLight && highlightDark) {
highlightLight.disabled = isDark;
highlightDark.disabled = !isDark;
}
}

function syncTheme(theme) {
const isDark = theme === 'dark';
document.documentElement.setAttribute('data-bs-theme', theme);
updateThemeIcon(isDark);
updateMarkdownTheme(isDark);
updateHighlightTheme(isDark);
}

const initialTheme = getTheme();
syncTheme(initialTheme);

if (themeToggle) {
themeToggle.addEventListener('click', (e) => {
e.preventDefault();
const isDark = document.documentElement.getAttribute('data-bs-theme') === 'dark';
const next = isDark ? 'light' : 'dark';
localStorage.setItem('theme', next);
syncTheme(next);
});
}

if (prefersDarkMedia.addEventListener) {
prefersDarkMedia.addEventListener('change', () => {
if (!localStorage.getItem('theme')) {
syncTheme(getTheme());
}
});
}

if (btn) {
const toggleBackToTop = () => {
btn.classList.toggle('d-none', window.scrollY <= 300);
};
window.addEventListener('scroll', toggleBackToTop, { passive: true });
toggleBackToTop();
btn.addEventListener('click', () => window.scrollTo({ top: 0, behavior: 'smooth' }));
}
</script>
<script>hljs.highlightAll();</script>
</body>
</html>
1 change: 0 additions & 1 deletion forum/templates/forum/comment_edit.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
<h3 class="mb-4">编辑评论</h3>
<form method="post" class="comment-form">
{% csrf_token %}
{{ form.media }}
{{ form.as_p }}
<button type="submit" class="btn btn-primary">
<span>{% include 'forum/icons/pencil-square.svg' %}</span> 保存修改
Expand Down
3 changes: 1 addition & 2 deletions forum/templates/forum/post_create.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@
{% block title %}发新帖{% endblock %}

{% block content %}
<div class="post-create-container" style="max-width: 800px; margin: 0 auto;">
<div class="post-create-container" style="max-width: 1200px; width: 100%; margin: 0 auto;">
<h1 class="mb-4">发新帖</h1>
<form method="post" class="form-container">
{% csrf_token %}
{{ form.media }}
{{ form.as_p }}
<button type="submit" class="btn btn-primary btn-lg">
发布文章
Expand Down
Loading
Loading