mirror of
https://github.com/DMOJ/online-judge.git
synced 2024-11-25 16:32:37 +08:00
Switch markdown editor to Martor (#1159)
This commit is contained in:
parent
12860ede66
commit
196311ab17
1
.gitignore
vendored
1
.gitignore
vendored
@ -10,4 +10,5 @@ resources/style.css
|
||||
resources/content-description.css
|
||||
resources/ranks.css
|
||||
resources/table.css
|
||||
resources/martor-description.css
|
||||
sass_processed
|
||||
|
@ -237,6 +237,7 @@ INSTALLED_APPS += (
|
||||
'statici18n',
|
||||
'impersonate',
|
||||
'django_jinja',
|
||||
'martor',
|
||||
)
|
||||
|
||||
MIDDLEWARE = (
|
||||
@ -386,6 +387,7 @@ MARKDOWN_USER_LARGE_STYLE = {
|
||||
}
|
||||
|
||||
MARKDOWN_STYLES = {
|
||||
'default': MARKDOWN_DEFAULT_STYLE,
|
||||
'comment': MARKDOWN_DEFAULT_STYLE,
|
||||
'self-description': MARKDOWN_USER_LARGE_STYLE,
|
||||
'problem': MARKDOWN_ADMIN_EDITABLE_STYLE,
|
||||
@ -400,6 +402,22 @@ MARKDOWN_STYLES = {
|
||||
'ticket': MARKDOWN_USER_LARGE_STYLE,
|
||||
}
|
||||
|
||||
MARTOR_ENABLE_CONFIGS = {
|
||||
'imgur': 'true',
|
||||
'mention': 'false',
|
||||
'jquery': 'true',
|
||||
'living': 'false',
|
||||
'spellcheck': 'false',
|
||||
'hljs': 'false',
|
||||
}
|
||||
MARTOR_MARKDOWNIFY_URL = '/widgets/preview/default'
|
||||
MARTOR_SEARCH_USERS_URL = '/widgets/martor/search-user'
|
||||
MARTOR_UPLOAD_URL = '/widgets/martor/upload-image'
|
||||
|
||||
# Directory under MEDIA_ROOT to use to store image uploaded through martor.
|
||||
MARTOR_UPLOAD_MEDIA_DIR = 'martor'
|
||||
MARTOR_UPLOAD_SAFE_EXTS = {'.jpg', '.png', '.gif'}
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases
|
||||
|
||||
|
10
dmoj/urls.py
10
dmoj/urls.py
@ -5,10 +5,11 @@ from django.contrib.auth import views as auth_views
|
||||
from django.contrib.sitemaps.views import sitemap
|
||||
from django.http import Http404, HttpResponsePermanentRedirect
|
||||
from django.templatetags.static import static
|
||||
from django.urls import reverse
|
||||
from django.urls import path, reverse
|
||||
from django.utils.functional import lazystr
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.views.generic import RedirectView
|
||||
from martor.views import markdown_search_user
|
||||
|
||||
from judge.feed import AtomBlogFeed, AtomCommentFeed, AtomProblemFeed, BlogFeed, CommentFeed, ProblemFeed
|
||||
from judge.forms import CustomAuthenticationForm
|
||||
@ -23,6 +24,7 @@ from judge.views.register import ActivationView, RegistrationView
|
||||
from judge.views.select2 import AssigneeSelect2View, CommentSelect2View, ContestSelect2View, \
|
||||
ContestUserSearchSelect2View, OrganizationSelect2View, ProblemSelect2View, TicketUserSelect2View, \
|
||||
UserSearchSelect2View, UserSelect2View
|
||||
from judge.views.widgets import martor_image_uploader
|
||||
|
||||
admin.autodiscover()
|
||||
|
||||
@ -292,6 +294,7 @@ urlpatterns = [
|
||||
])),
|
||||
|
||||
url(r'^preview/', include([
|
||||
url(r'^default$', preview.DefaultMarkdownPreviewView.as_view(), name='default_preview'),
|
||||
url(r'^problem$', preview.ProblemMarkdownPreviewView.as_view(), name='problem_preview'),
|
||||
url(r'^blog$', preview.BlogMarkdownPreviewView.as_view(), name='blog_preview'),
|
||||
url(r'^contest$', preview.ContestMarkdownPreviewView.as_view(), name='contest_preview'),
|
||||
@ -302,6 +305,11 @@ urlpatterns = [
|
||||
url(r'^license$', preview.LicenseMarkdownPreviewView.as_view(), name='license_preview'),
|
||||
url(r'^ticket$', preview.TicketMarkdownPreviewView.as_view(), name='ticket_preview'),
|
||||
])),
|
||||
|
||||
path('martor/', include([
|
||||
path('upload-image', martor_image_uploader, name='martor_image_uploader'),
|
||||
path('search-user', markdown_search_user, name='martor_search_user'),
|
||||
])),
|
||||
])),
|
||||
|
||||
url(r'^feed/', include([
|
||||
|
@ -5,7 +5,7 @@ from django.utils.translation import gettext_lazy as _, ungettext
|
||||
from reversion.admin import VersionAdmin
|
||||
|
||||
from judge.models import Comment
|
||||
from judge.widgets import AdminHeavySelect2Widget, HeavyPreviewAdminPageDownWidget
|
||||
from judge.widgets import AdminHeavySelect2Widget, AdminMartorWidget
|
||||
|
||||
|
||||
class CommentForm(ModelForm):
|
||||
@ -13,9 +13,8 @@ class CommentForm(ModelForm):
|
||||
widgets = {
|
||||
'author': AdminHeavySelect2Widget(data_view='profile_select2'),
|
||||
'parent': AdminHeavySelect2Widget(data_view='comment_select2'),
|
||||
'body': AdminMartorWidget(attrs={'data-markdownfy-url': reverse_lazy('comment_preview')}),
|
||||
}
|
||||
if HeavyPreviewAdminPageDownWidget is not None:
|
||||
widgets['body'] = HeavyPreviewAdminPageDownWidget(preview=reverse_lazy('comment_preview'))
|
||||
|
||||
|
||||
class CommentAdmin(VersionAdmin):
|
||||
|
@ -15,8 +15,8 @@ from django_ace import AceWidget
|
||||
from judge.models import Contest, ContestProblem, ContestSubmission, Profile, Rating
|
||||
from judge.ratings import rate_contest
|
||||
from judge.utils.views import NoBatchDeleteMixin
|
||||
from judge.widgets import AdminHeavySelect2MultipleWidget, AdminHeavySelect2Widget, AdminPagedownWidget, \
|
||||
AdminSelect2MultipleWidget, AdminSelect2Widget, HeavyPreviewAdminPageDownWidget
|
||||
from judge.widgets import AdminHeavySelect2MultipleWidget, AdminHeavySelect2Widget, AdminMartorWidget, \
|
||||
AdminSelect2MultipleWidget, AdminSelect2Widget
|
||||
|
||||
|
||||
class AdminHeavySelect2Widget(AdminHeavySelect2Widget):
|
||||
@ -39,11 +39,9 @@ class ContestTagAdmin(admin.ModelAdmin):
|
||||
actions_on_top = True
|
||||
actions_on_bottom = True
|
||||
form = ContestTagForm
|
||||
|
||||
if AdminPagedownWidget is not None:
|
||||
formfield_overrides = {
|
||||
TextField: {'widget': AdminPagedownWidget},
|
||||
}
|
||||
formfield_overrides = {
|
||||
TextField: {'widget': AdminMartorWidget},
|
||||
}
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
super(ContestTagAdmin, self).save_model(request, obj, form, change)
|
||||
@ -105,11 +103,9 @@ class ContestForm(ModelForm):
|
||||
attrs={'style': 'width: 100%'}),
|
||||
'view_contest_scoreboard': AdminHeavySelect2MultipleWidget(data_view='profile_select2',
|
||||
attrs={'style': 'width: 100%'}),
|
||||
'description': AdminMartorWidget(attrs={'data-markdownfy-url': reverse_lazy('contest_preview')}),
|
||||
}
|
||||
|
||||
if HeavyPreviewAdminPageDownWidget is not None:
|
||||
widgets['description'] = HeavyPreviewAdminPageDownWidget(preview=reverse_lazy('contest_preview'))
|
||||
|
||||
|
||||
class ContestAdmin(NoBatchDeleteMixin, VersionAdmin):
|
||||
fieldsets = (
|
||||
|
@ -9,7 +9,7 @@ from reversion.admin import VersionAdmin
|
||||
|
||||
from judge.dblock import LockModel
|
||||
from judge.models import NavigationBar
|
||||
from judge.widgets import AdminHeavySelect2MultipleWidget, AdminHeavySelect2Widget, HeavyPreviewAdminPageDownWidget
|
||||
from judge.widgets import AdminHeavySelect2MultipleWidget, AdminHeavySelect2Widget, AdminMartorWidget
|
||||
|
||||
|
||||
class NavigationBarAdmin(DraggableMPTTAdmin):
|
||||
@ -49,12 +49,10 @@ class BlogPostForm(ModelForm):
|
||||
class Meta:
|
||||
widgets = {
|
||||
'authors': AdminHeavySelect2MultipleWidget(data_view='profile_select2', attrs={'style': 'width: 100%'}),
|
||||
'content': AdminMartorWidget(attrs={'data-markdownfy-url': reverse_lazy('blog_preview')}),
|
||||
'summary': AdminMartorWidget(attrs={'data-markdownfy-url': reverse_lazy('blog_preview')}),
|
||||
}
|
||||
|
||||
if HeavyPreviewAdminPageDownWidget is not None:
|
||||
widgets['content'] = HeavyPreviewAdminPageDownWidget(preview=reverse_lazy('blog_preview'))
|
||||
widgets['summary'] = HeavyPreviewAdminPageDownWidget(preview=reverse_lazy('blog_preview'))
|
||||
|
||||
|
||||
class BlogPostAdmin(VersionAdmin):
|
||||
fieldsets = (
|
||||
@ -84,16 +82,13 @@ class SolutionForm(ModelForm):
|
||||
widgets = {
|
||||
'authors': AdminHeavySelect2MultipleWidget(data_view='profile_select2', attrs={'style': 'width: 100%'}),
|
||||
'problem': AdminHeavySelect2Widget(data_view='problem_select2', attrs={'style': 'width: 250px'}),
|
||||
'content': AdminMartorWidget(attrs={'data-markdownfy-url': reverse_lazy('solution_preview')}),
|
||||
}
|
||||
|
||||
if HeavyPreviewAdminPageDownWidget is not None:
|
||||
widgets['content'] = HeavyPreviewAdminPageDownWidget(preview=reverse_lazy('solution_preview'))
|
||||
|
||||
|
||||
class LicenseForm(ModelForm):
|
||||
class Meta:
|
||||
if HeavyPreviewAdminPageDownWidget is not None:
|
||||
widgets = {'text': HeavyPreviewAdminPageDownWidget(preview=reverse_lazy('license_preview'))}
|
||||
widgets = {'text': AdminMartorWidget(attrs={'data-markdownfy-url': reverse_lazy('license_preview')})}
|
||||
|
||||
|
||||
class LicenseAdmin(admin.ModelAdmin):
|
||||
|
@ -6,7 +6,7 @@ from django.utils.translation import gettext, gettext_lazy as _
|
||||
from reversion.admin import VersionAdmin
|
||||
|
||||
from judge.models import Organization
|
||||
from judge.widgets import AdminHeavySelect2MultipleWidget, AdminHeavySelect2Widget, HeavyPreviewAdminPageDownWidget
|
||||
from judge.widgets import AdminHeavySelect2MultipleWidget, AdminHeavySelect2Widget, AdminMartorWidget
|
||||
|
||||
|
||||
class OrganizationForm(ModelForm):
|
||||
@ -14,9 +14,8 @@ class OrganizationForm(ModelForm):
|
||||
widgets = {
|
||||
'admins': AdminHeavySelect2MultipleWidget(data_view='profile_select2'),
|
||||
'registrant': AdminHeavySelect2Widget(data_view='profile_select2'),
|
||||
'about': AdminMartorWidget(attrs={'data-markdownfy-url': reverse_lazy('organization_preview')}),
|
||||
}
|
||||
if HeavyPreviewAdminPageDownWidget is not None:
|
||||
widgets['about'] = HeavyPreviewAdminPageDownWidget(preview=reverse_lazy('organization_preview'))
|
||||
|
||||
|
||||
class OrganizationAdmin(VersionAdmin):
|
||||
|
@ -12,8 +12,8 @@ from reversion.admin import VersionAdmin
|
||||
|
||||
from judge.models import LanguageLimit, Problem, ProblemClarification, ProblemTranslation, Profile, Solution
|
||||
from judge.utils.views import NoBatchDeleteMixin
|
||||
from judge.widgets import AdminHeavySelect2MultipleWidget, AdminSelect2MultipleWidget, AdminSelect2Widget, \
|
||||
CheckboxSelectMultipleWithSelectAll, HeavyPreviewAdminPageDownWidget, HeavyPreviewPageDownWidget
|
||||
from judge.widgets import AdminHeavySelect2MultipleWidget, AdminMartorWidget, AdminSelect2MultipleWidget, \
|
||||
AdminSelect2Widget, CheckboxSelectMultipleWithSelectAll
|
||||
|
||||
|
||||
class ProblemForm(ModelForm):
|
||||
@ -40,9 +40,8 @@ class ProblemForm(ModelForm):
|
||||
attrs={'style': 'width: 100%'}),
|
||||
'types': AdminSelect2MultipleWidget,
|
||||
'group': AdminSelect2Widget,
|
||||
'description': AdminMartorWidget(attrs={'data-markdownfy-url': reverse_lazy('problem_preview')}),
|
||||
}
|
||||
if HeavyPreviewAdminPageDownWidget is not None:
|
||||
widgets['description'] = HeavyPreviewAdminPageDownWidget(preview=reverse_lazy('problem_preview'))
|
||||
|
||||
|
||||
class ProblemCreatorListFilter(admin.SimpleListFilter):
|
||||
@ -71,8 +70,7 @@ class LanguageLimitInline(admin.TabularInline):
|
||||
|
||||
class ProblemClarificationForm(ModelForm):
|
||||
class Meta:
|
||||
if HeavyPreviewPageDownWidget is not None:
|
||||
widgets = {'description': HeavyPreviewPageDownWidget(preview=reverse_lazy('comment_preview'))}
|
||||
widgets = {'description': AdminMartorWidget(attrs={'data-markdownfy-url': reverse_lazy('comment_preview')})}
|
||||
|
||||
|
||||
class ProblemClarificationInline(admin.StackedInline):
|
||||
@ -90,11 +88,9 @@ class ProblemSolutionForm(ModelForm):
|
||||
class Meta:
|
||||
widgets = {
|
||||
'authors': AdminHeavySelect2MultipleWidget(data_view='profile_select2', attrs={'style': 'width: 100%'}),
|
||||
'content': AdminMartorWidget(attrs={'data-markdownfy-url': reverse_lazy('solution_preview')}),
|
||||
}
|
||||
|
||||
if HeavyPreviewAdminPageDownWidget is not None:
|
||||
widgets['content'] = HeavyPreviewAdminPageDownWidget(preview=reverse_lazy('solution_preview'))
|
||||
|
||||
|
||||
class ProblemSolutionInline(admin.StackedInline):
|
||||
model = Solution
|
||||
@ -105,8 +101,7 @@ class ProblemSolutionInline(admin.StackedInline):
|
||||
|
||||
class ProblemTranslationForm(ModelForm):
|
||||
class Meta:
|
||||
if HeavyPreviewAdminPageDownWidget is not None:
|
||||
widgets = {'description': HeavyPreviewAdminPageDownWidget(preview=reverse_lazy('problem_preview'))}
|
||||
widgets = {'description': AdminMartorWidget(attrs={'data-markdownfy-url': reverse_lazy('problem_preview')})}
|
||||
|
||||
|
||||
class ProblemTranslationInline(admin.StackedInline):
|
||||
|
@ -1,5 +1,6 @@
|
||||
from django.contrib import admin
|
||||
from django.forms import ModelForm
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.html import format_html
|
||||
from django.utils.translation import gettext, gettext_lazy as _, ungettext
|
||||
from reversion.admin import VersionAdmin
|
||||
@ -7,7 +8,7 @@ from reversion.admin import VersionAdmin
|
||||
from django_ace import AceWidget
|
||||
from judge.models import Profile
|
||||
from judge.utils.views import NoBatchDeleteMixin
|
||||
from judge.widgets import AdminPagedownWidget, AdminSelect2Widget
|
||||
from judge.widgets import AdminMartorWidget, AdminSelect2Widget
|
||||
|
||||
|
||||
class ProfileForm(ModelForm):
|
||||
@ -26,9 +27,8 @@ class ProfileForm(ModelForm):
|
||||
'language': AdminSelect2Widget,
|
||||
'ace_theme': AdminSelect2Widget,
|
||||
'current_contest': AdminSelect2Widget,
|
||||
'about': AdminMartorWidget(attrs={'data-markdownfy-url': reverse_lazy('profile_preview')}),
|
||||
}
|
||||
if AdminPagedownWidget is not None:
|
||||
widgets['about'] = AdminPagedownWidget
|
||||
|
||||
|
||||
class TimezoneFilter(admin.SimpleListFilter):
|
||||
|
@ -11,7 +11,7 @@ from reversion.admin import VersionAdmin
|
||||
|
||||
from django_ace import AceWidget
|
||||
from judge.models import Judge, Problem
|
||||
from judge.widgets import AdminHeavySelect2MultipleWidget, AdminPagedownWidget
|
||||
from judge.widgets import AdminHeavySelect2MultipleWidget, AdminMartorWidget
|
||||
|
||||
|
||||
class LanguageForm(ModelForm):
|
||||
@ -23,8 +23,7 @@ class LanguageForm(ModelForm):
|
||||
widget=AdminHeavySelect2MultipleWidget(data_view='problem_select2'))
|
||||
|
||||
class Meta:
|
||||
if AdminPagedownWidget is not None:
|
||||
widgets = {'description': AdminPagedownWidget}
|
||||
widgets = {'description': AdminMartorWidget}
|
||||
|
||||
|
||||
class LanguageAdmin(VersionAdmin):
|
||||
@ -70,9 +69,7 @@ django.jQuery(document).ready(function ($) {{
|
||||
|
||||
class JudgeAdminForm(ModelForm):
|
||||
class Meta:
|
||||
widgets = {'auth_key': GenerateKeyTextInput}
|
||||
if AdminPagedownWidget is not None:
|
||||
widgets['description'] = AdminPagedownWidget
|
||||
widgets = {'auth_key': GenerateKeyTextInput, 'description': AdminMartorWidget}
|
||||
|
||||
|
||||
class JudgeAdmin(VersionAdmin):
|
||||
@ -86,6 +83,9 @@ class JudgeAdmin(VersionAdmin):
|
||||
)
|
||||
list_display = ('name', 'online', 'start_time', 'ping', 'load', 'last_ip')
|
||||
ordering = ['-online', 'name']
|
||||
formfield_overrides = {
|
||||
TextField: {'widget': AdminMartorWidget},
|
||||
}
|
||||
|
||||
def get_urls(self):
|
||||
return ([url(r'^(\d+)/disconnect/$', self.disconnect_view, name='judge_judge_disconnect'),
|
||||
@ -113,8 +113,3 @@ class JudgeAdmin(VersionAdmin):
|
||||
if result and obj is not None:
|
||||
return not obj.online
|
||||
return result
|
||||
|
||||
if AdminPagedownWidget is not None:
|
||||
formfield_overrides = {
|
||||
TextField: {'widget': AdminPagedownWidget},
|
||||
}
|
||||
|
@ -4,16 +4,15 @@ from django.forms import ModelForm
|
||||
from django.urls import reverse_lazy
|
||||
|
||||
from judge.models import TicketMessage
|
||||
from judge.widgets import AdminHeavySelect2MultipleWidget, AdminHeavySelect2Widget, HeavyPreviewAdminPageDownWidget
|
||||
from judge.widgets import AdminHeavySelect2MultipleWidget, AdminHeavySelect2Widget, AdminMartorWidget
|
||||
|
||||
|
||||
class TicketMessageForm(ModelForm):
|
||||
class Meta:
|
||||
widgets = {
|
||||
'user': AdminHeavySelect2Widget(data_view='profile_select2', attrs={'style': 'width: 100%'}),
|
||||
'body': AdminMartorWidget(attrs={'data-markdownfy-url': reverse_lazy('ticket_preview')}),
|
||||
}
|
||||
if HeavyPreviewAdminPageDownWidget is not None:
|
||||
widgets['body'] = HeavyPreviewAdminPageDownWidget(preview=reverse_lazy('ticket_preview'))
|
||||
|
||||
|
||||
class TicketMessageInline(StackedInline):
|
||||
|
@ -5,7 +5,7 @@ from django.views.generic.base import ContextMixin, TemplateResponseMixin, View
|
||||
class MarkdownPreviewView(TemplateResponseMixin, ContextMixin, View):
|
||||
def post(self, request, *args, **kwargs):
|
||||
try:
|
||||
self.preview_data = data = request.POST['preview']
|
||||
self.preview_data = data = request.POST['content']
|
||||
except KeyError:
|
||||
return HttpResponseBadRequest('No preview data specified.')
|
||||
|
||||
@ -48,3 +48,7 @@ class LicenseMarkdownPreviewView(MarkdownPreviewView):
|
||||
|
||||
class TicketMarkdownPreviewView(MarkdownPreviewView):
|
||||
template_name = 'ticket/preview.html'
|
||||
|
||||
|
||||
class DefaultMarkdownPreviewView(MarkdownPreviewView):
|
||||
template_name = 'default-preview.html'
|
||||
|
@ -1,11 +1,18 @@
|
||||
import json
|
||||
import os
|
||||
import uuid
|
||||
from urllib.parse import urljoin
|
||||
|
||||
import requests
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.core.files.storage import default_storage
|
||||
from django.http import Http404, HttpResponse, HttpResponseBadRequest, HttpResponseForbidden, HttpResponseRedirect
|
||||
from django.utils.translation import gettext as _
|
||||
from django.views.decorators.http import require_POST
|
||||
from django.views.generic import View
|
||||
from martor.api import imgur_uploader
|
||||
|
||||
from judge.models import Submission
|
||||
|
||||
@ -67,3 +74,29 @@ class DetectTimezone(View):
|
||||
'askgeo': self.askgeo,
|
||||
'geonames': self.geonames,
|
||||
}.get(backend, self.default)(lat, long)
|
||||
|
||||
|
||||
def django_uploader(image):
|
||||
ext = os.path.splitext(image.name)[1]
|
||||
if ext not in settings.MARTOR_UPLOAD_SAFE_EXTS:
|
||||
ext = '.png'
|
||||
name = str(uuid.uuid4()) + ext
|
||||
default_storage.save(os.path.join(settings.MARTOR_UPLOAD_MEDIA_DIR, name), image)
|
||||
url_base = getattr(settings, 'MARTOR_UPLOAD_URL_PREFIX',
|
||||
urljoin(settings.MEDIA_URL, settings.MARTOR_UPLOAD_MEDIA_DIR))
|
||||
if not url_base.endswith('/'):
|
||||
url_base += '/'
|
||||
return json.dumps({'status': 200, 'name': '', 'link': urljoin(url_base, name)})
|
||||
|
||||
|
||||
@login_required
|
||||
def martor_image_uploader(request):
|
||||
if request.method != 'POST' or not request.is_ajax() or 'markdown-image-upload' not in request.FILES:
|
||||
return HttpResponseBadRequest('Invalid request')
|
||||
|
||||
image = request.FILES['markdown-image-upload']
|
||||
if request.user.is_staff:
|
||||
data = django_uploader(image)
|
||||
else:
|
||||
data = imgur_uploader(image)
|
||||
return HttpResponse(data, content_type='application/json')
|
||||
|
@ -1,4 +1,5 @@
|
||||
from judge.widgets.checkbox import CheckboxSelectMultipleWithSelectAll
|
||||
from judge.widgets.martor import *
|
||||
from judge.widgets.mixins import CompressorWidgetMixin
|
||||
from judge.widgets.pagedown import *
|
||||
from judge.widgets.select2 import *
|
||||
|
17
judge/widgets/martor.py
Normal file
17
judge/widgets/martor.py
Normal file
@ -0,0 +1,17 @@
|
||||
from martor.widgets import AdminMartorWidget as OldAdminMartorWidget, MartorWidget as OldMartorWidget
|
||||
|
||||
__all__ = ['MartorWidget', 'AdminMartorWidget']
|
||||
|
||||
|
||||
class MartorWidget(OldMartorWidget):
|
||||
class Media:
|
||||
css = {
|
||||
'all': ['martor-description.css'],
|
||||
}
|
||||
js = ['martor-mathjax.js']
|
||||
|
||||
|
||||
class AdminMartorWidget(OldAdminMartorWidget):
|
||||
class Media:
|
||||
css = MartorWidget.Media.css
|
||||
js = ['admin/js/jquery.init.js', 'martor-mathjax.js']
|
@ -14,7 +14,8 @@ if ! [ -x "$(command -v autoprefixer)" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
FILES=(sass_processed/style.css sass_processed/content-description.css sass_processed/table.css sass_processed/ranks.css)
|
||||
FILES=(sass_processed/style.css sass_processed/content-description.css sass_processed/table.css
|
||||
sass_processed/ranks.css sass_processed/martor-description.css)
|
||||
|
||||
cd `dirname $0`
|
||||
sass --update resources:sass_processed
|
||||
|
@ -31,3 +31,4 @@ celery
|
||||
-e git://github.com/DMOJ/ansi2html.git#egg=ansi2html
|
||||
sqlparse
|
||||
lupa
|
||||
-e git://github.com/DMOJ/martor.git#egg=martor
|
178
resources/base-description.scss
Normal file
178
resources/base-description.scss
Normal file
@ -0,0 +1,178 @@
|
||||
@import "vars";
|
||||
|
||||
@mixin content-description {
|
||||
line-height: 1.5em;
|
||||
font-size: 1em;
|
||||
font-family: "Segoe UI", "Lucida Grande", Arial, sans-serif;
|
||||
|
||||
p {
|
||||
margin: 1em 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-weight: normal;
|
||||
color: #111;
|
||||
margin-bottom: 0.75em;
|
||||
padding: 0;
|
||||
background: 0;
|
||||
}
|
||||
|
||||
h3, h4, h5, h6 {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.5em;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.6em;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1.4em;
|
||||
border-bottom: 1px solid #EEE;
|
||||
line-height: 1.225;
|
||||
padding-bottom: 0.3em;
|
||||
padding-top: 0.5em;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1.15em;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
color: #666;
|
||||
margin: 0;
|
||||
padding-left: 1.5em;
|
||||
border-left: 0.5em #EEE solid;
|
||||
}
|
||||
|
||||
hr {
|
||||
display: block;
|
||||
height: 0;
|
||||
border: 0;
|
||||
font-style: italic;
|
||||
border-bottom: 1px solid $border_gray;
|
||||
margin: 25px 0 20px 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
pre, code, kbd, samp, span.code {
|
||||
color: #000;
|
||||
page-break-inside: avoid;
|
||||
font-family: $monospace-fonts;
|
||||
font-size: 0.98em;
|
||||
}
|
||||
|
||||
code, span.code {
|
||||
font-family: $monospace-fonts !important;
|
||||
margin: 0 2px;
|
||||
padding: 0 5px;
|
||||
border: 1px solid $border_gray;
|
||||
background-color: #f8f8f8;
|
||||
border-radius: $widget_border_radius;
|
||||
font-size: 0.95em;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
pre {
|
||||
code, div.code {
|
||||
border: 0;
|
||||
line-height: 1em;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
font-size: 1em;
|
||||
color: black;
|
||||
}
|
||||
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
margin: 1.5em 0 1.5em 0;
|
||||
padding: 1em;
|
||||
border: 1px solid $border_gray;
|
||||
background-color: #f8f8f8;
|
||||
color: black;
|
||||
border-radius: $widget_border_radius;
|
||||
}
|
||||
|
||||
b, strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
dfn {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
ins {
|
||||
background: #ff9;
|
||||
color: #000;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
mark {
|
||||
background: #ff0;
|
||||
color: #000;
|
||||
font-style: italic;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
sub {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
padding: 0 0 0 2em !important;
|
||||
}
|
||||
|
||||
li p:last-child {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin: 0 0 0 2em;
|
||||
}
|
||||
|
||||
img {
|
||||
border: 0;
|
||||
-ms-interpolation-mode: bicubic;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
td {
|
||||
vertical-align: top;
|
||||
}
|
||||
}
|
@ -1,180 +1,7 @@
|
||||
@import "vars";
|
||||
@import "base-description";
|
||||
|
||||
.content-description {
|
||||
line-height: 1.5em;
|
||||
font-size: 1em;
|
||||
font-family: "Segoe UI", "Lucida Grande", Arial, sans-serif;
|
||||
|
||||
p {
|
||||
margin: 1em 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-weight: normal;
|
||||
color: #111;
|
||||
margin-bottom: 0.75em;
|
||||
padding: 0;
|
||||
background: 0;
|
||||
}
|
||||
|
||||
h3, h4, h5, h6 {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.5em;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.6em;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1.4em;
|
||||
border-bottom: 1px solid #EEE;
|
||||
line-height: 1.225;
|
||||
padding-bottom: 0.3em;
|
||||
padding-top: 0.5em;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1.15em;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
color: #666;
|
||||
margin: 0;
|
||||
padding-left: 1.5em;
|
||||
border-left: 0.5em #EEE solid;
|
||||
}
|
||||
|
||||
hr {
|
||||
display: block;
|
||||
height: 0;
|
||||
border: 0;
|
||||
font-style: italic;
|
||||
border-bottom: 1px solid $border_gray;
|
||||
margin: 25px 0 20px 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
pre, code, kbd, samp, span.code {
|
||||
color: #000;
|
||||
page-break-inside: avoid;
|
||||
font-family: $monospace-fonts;
|
||||
font-size: 0.98em;
|
||||
}
|
||||
|
||||
code, span.code {
|
||||
font-family: $monospace-fonts !important;
|
||||
margin: 0 2px;
|
||||
padding: 0 5px;
|
||||
border: 1px solid $border_gray;
|
||||
background-color: #f8f8f8;
|
||||
border-radius: $widget_border_radius;
|
||||
font-size: 0.95em;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
pre {
|
||||
code, div.code {
|
||||
border: 0;
|
||||
line-height: 1em;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
font-size: 1em;
|
||||
color: black;
|
||||
}
|
||||
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
margin: 1.5em 0 1.5em 0;
|
||||
padding: 1em;
|
||||
border: 1px solid $border_gray;
|
||||
background-color: #f8f8f8;
|
||||
color: black;
|
||||
border-radius: $widget_border_radius;
|
||||
}
|
||||
|
||||
b, strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
dfn {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
ins {
|
||||
background: #ff9;
|
||||
color: #000;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
mark {
|
||||
background: #ff0;
|
||||
color: #000;
|
||||
font-style: italic;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
sub {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
padding: 0 0 0 2em !important;
|
||||
}
|
||||
|
||||
li p:last-child {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin: 0 0 0 2em;
|
||||
}
|
||||
|
||||
img {
|
||||
border: 0;
|
||||
-ms-interpolation-mode: bicubic;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
td {
|
||||
vertical-align: top;
|
||||
}
|
||||
@include content-description;
|
||||
}
|
||||
|
||||
.display-math {
|
||||
|
@ -19,7 +19,7 @@ $(function () {
|
||||
if (text) {
|
||||
$preview.addClass('dmmd-preview-stale');
|
||||
$.post(preview_url, {
|
||||
preview: text,
|
||||
content: text,
|
||||
csrfmiddlewaretoken: $.cookie('csrftoken')
|
||||
}, function (result) {
|
||||
$content.html(result);
|
||||
|
35
resources/martor-description.scss
Normal file
35
resources/martor-description.scss
Normal file
@ -0,0 +1,35 @@
|
||||
@import "base-description";
|
||||
|
||||
form .martor-preview {
|
||||
@include content-description;
|
||||
|
||||
.martor-preview ul li {
|
||||
list-style: unset !important;
|
||||
}
|
||||
|
||||
.martor-preview ul, .martor-preview ol {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.main-martor-fullscreen {
|
||||
z-index: 99999 !important;
|
||||
}
|
||||
|
||||
.section-martor {
|
||||
div[data-tab="editor-tab-description"] {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.markdown-selector.markdown-emoji {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.tab-martor-menu a.item {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.martor-field {
|
||||
height: 400px;
|
||||
}
|
||||
}
|
34
resources/martor-mathjax.js
Normal file
34
resources/martor-mathjax.js
Normal file
@ -0,0 +1,34 @@
|
||||
jQuery(function ($) {
|
||||
$(document).on('martor:preview', function (e, $content) {
|
||||
function update_math() {
|
||||
MathJax.Hub.Queue(['Typeset', MathJax.Hub, $content[0]], function () {
|
||||
$content.find('.tex-image').hide();
|
||||
$content.find('.tex-text').show();
|
||||
});
|
||||
}
|
||||
|
||||
var $jax = $content.find('.require-mathjax-support');
|
||||
if ($jax.length) {
|
||||
if (!('MathJax' in window)) {
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: $jax.attr('data-config'),
|
||||
dataType: 'script',
|
||||
cache: true,
|
||||
success: function () {
|
||||
window.MathJax.loader = {typeset: false};
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-AMS_HTML',
|
||||
dataType: 'script',
|
||||
cache: true,
|
||||
success: update_math
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
update_math();
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
1
templates/default-preview.html
Normal file
1
templates/default-preview.html
Normal file
@ -0,0 +1 @@
|
||||
{{ preview_data|markdown('default') }}
|
Loading…
Reference in New Issue
Block a user