├── maltose ├── maltose │ ├── __init__.py │ ├── management │ │ ├── __init__.py │ │ └── commands │ │ │ ├── __init__.py │ │ │ ├── preview.py │ │ │ └── clear_migrations.py │ ├── dev.py │ ├── context_processors.py │ ├── middleware.py │ ├── templates │ │ └── registration │ │ │ ├── logged_out.html │ │ │ └── login.html │ ├── wsgi.py │ ├── common.py │ ├── views.py │ ├── urls.py │ ├── models.py │ └── settings.py ├── article │ ├── management │ │ ├── __init__.py │ │ └── commands │ │ │ ├── __init__.py │ │ │ ├── push.py │ │ │ ├── update.py │ │ │ └── import.py │ ├── migrations │ │ ├── __init__.py │ │ └── 0001_initial.py │ ├── views │ │ ├── __init__.py │ │ ├── view.py │ │ └── api.py │ ├── apps.py │ ├── __init__.py │ ├── forms.py │ ├── admin.py │ ├── context_processors.py │ ├── signals.py │ ├── tests.py │ ├── urls.py │ ├── models.py │ └── build.py ├── sundries │ ├── migrations │ │ ├── __init__.py │ │ └── 0001_initial.py │ ├── __init__.py │ ├── tests.py │ ├── views.py │ ├── context_processors.py │ ├── admin.py │ ├── apps.py │ ├── models.py │ └── signals.py ├── __version__.py ├── editor │ ├── favicon.ico │ ├── manifest.json │ ├── static │ │ ├── css │ │ │ ├── main.84c36446.chunk.css │ │ │ └── main.84c36446.chunk.css.map │ │ └── js │ │ │ ├── runtime~main.e21c4546.js │ │ │ ├── 1.75117139.chunk.js │ │ │ ├── 1.75117139.chunk.js.map │ │ │ ├── runtime~main.e21c4546.js.map │ │ │ ├── main.d4aa6365.chunk.js │ │ │ └── main.d4aa6365.chunk.js.map │ ├── precache-manifest.f7019b14d5f5d95e64545166a760a11b.js │ ├── asset-manifest.json │ ├── service-worker.js │ └── index.html └── __init__.py ├── requirements.txt ├── example.png ├── MANIFEST.in ├── maltose-cli.py ├── README.md ├── .gitignore ├── setup.py └── LICENSE /maltose/maltose/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /maltose/article/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /maltose/article/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /maltose/maltose/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /maltose/sundries/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /maltose/article/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /maltose/maltose/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django 2 | Pillow 3 | Markdown 4 | Pygments 5 | colorama 6 | -------------------------------------------------------------------------------- /example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaltoseEditor/maltose/HEAD/example.png -------------------------------------------------------------------------------- /maltose/__version__.py: -------------------------------------------------------------------------------- 1 | VERSION = (0, 1, 7) 2 | 3 | __version__ = '.'.join(map(str, VERSION)) -------------------------------------------------------------------------------- /maltose/sundries/__init__.py: -------------------------------------------------------------------------------- 1 | default_app_config = 'maltose.sundries.apps.SundriesConfig' 2 | -------------------------------------------------------------------------------- /maltose/sundries/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /maltose/sundries/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md LICENSE maltose/editor/static/css/* maltose/editor/static/js/* maltose/editor/* -------------------------------------------------------------------------------- /maltose/editor/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaltoseEditor/maltose/HEAD/maltose/editor/favicon.ico -------------------------------------------------------------------------------- /maltose-cli.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | from maltose import execute 3 | 4 | 5 | if __name__ == "__main__": 6 | execute() 7 | -------------------------------------------------------------------------------- /maltose/article/views/__init__.py: -------------------------------------------------------------------------------- 1 | from .api import * 2 | from .view import * 3 | 4 | __all__ = api.__all__ + view.__all__ 5 | -------------------------------------------------------------------------------- /maltose/sundries/context_processors.py: -------------------------------------------------------------------------------- 1 | from .models import FriendLink 2 | 3 | 4 | def get_all_friend(request): 5 | return {'friends': FriendLink.objects.all()} 6 | -------------------------------------------------------------------------------- /maltose/sundries/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import * 4 | 5 | 6 | @admin.register(FriendLink) 7 | class FriendLinkAdmin(admin.ModelAdmin): 8 | pass 9 | -------------------------------------------------------------------------------- /maltose/article/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ArticleConfig(AppConfig): 5 | name = 'maltose.article' 6 | 7 | def ready(self): 8 | import maltose.article.signals 9 | -------------------------------------------------------------------------------- /maltose/sundries/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class SundriesConfig(AppConfig): 5 | name = 'maltose.sundries' 6 | 7 | def ready(self): 8 | import maltose.sundries.signals 9 | -------------------------------------------------------------------------------- /maltose/maltose/dev.py: -------------------------------------------------------------------------------- 1 | from .settings import * 2 | 3 | DATABASES = { 4 | 'default': { 5 | 'ENGINE': 'django.db.backends.sqlite3', 6 | 'NAME': os.path.join(BASE_DIR, 'test.db.sqlite3'), 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /maltose/sundries/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | __all__ = [ 4 | 'FriendLink', 5 | ] 6 | 7 | 8 | class FriendLink(models.Model): 9 | name = models.CharField("名字", max_length=50) 10 | link = models.URLField("网址") 11 | 12 | def __str__(self): 13 | return self.name 14 | -------------------------------------------------------------------------------- /maltose/article/management/commands/push.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | 3 | from maltose.maltose.common import push 4 | 5 | 6 | class Command(BaseCommand): 7 | help = '推送生成的静态文件到远程仓库' 8 | 9 | def handle(self, *args, **options): 10 | process = push() 11 | process.wait() 12 | -------------------------------------------------------------------------------- /maltose/article/__init__.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | import markdown 3 | 4 | default_app_config = 'maltose.article.apps.ArticleConfig' 5 | 6 | 7 | def render(source): 8 | """ 9 | 对source进行markdown渲染处理 10 | 11 | :param source: markdown原始数据 12 | :return: markdown渲染后的结果 13 | """ 14 | return markdown.markdown(source, **settings.MARKDOWN) 15 | -------------------------------------------------------------------------------- /maltose/maltose/context_processors.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | 3 | 4 | def get_debug(request): 5 | host = settings.HOMEPAGE.split("://")[1].split("/")[0] 6 | if request.get_host() != host: 7 | return {"debug": True} 8 | else: 9 | return {"debug": False} 10 | 11 | 12 | def get_settings(request): 13 | return {"settings": settings} 14 | -------------------------------------------------------------------------------- /maltose/editor/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Maltose editor", 3 | "name": "Maltose editor for blog manage", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } -------------------------------------------------------------------------------- /maltose/maltose/middleware.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpResponseBadRequest 2 | from django.utils.deprecation import MiddlewareMixin 3 | from django.conf import settings 4 | 5 | 6 | class RequestHostMiddleware(MiddlewareMixin): 7 | 8 | def process_request(self, request): 9 | if request.get_host() == "testserver": 10 | request.META['HTTP_HOST'] = settings.HOMEPAGE.split("://")[1].split("/")[0] 11 | -------------------------------------------------------------------------------- /maltose/maltose/templates/registration/logged_out.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 登出 9 | 10 | 11 | 12 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /maltose/maltose/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for maltose project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.1/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'maltose.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /maltose/maltose/management/commands/preview.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from django.core.management.base import BaseCommand 4 | from django.conf import settings 5 | 6 | 7 | class Command(BaseCommand): 8 | help = '启动模拟Github Page的预览服务器' 9 | 10 | def handle(self, *args, **options): 11 | os.chdir(settings.BLOG_REPOSITORIES) 12 | if os.name == "posix": 13 | os.system("python3 -m http.server") 14 | else: 15 | os.system("python -m http.server") 16 | -------------------------------------------------------------------------------- /maltose/sundries/signals.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from django.db.models.signals import pre_delete, post_save, post_init 4 | from django.dispatch import receiver 5 | from django.conf import settings 6 | 7 | from maltose.article.build import update_all 8 | 9 | from .models import FriendLink 10 | 11 | 12 | @receiver(post_save, sender=FriendLink) 13 | def post_update_article(sender, instance, **kwargs): 14 | update_all() 15 | 16 | 17 | @receiver(pre_delete, sender=FriendLink) 18 | def delete_article(sender, instance, **kwargs): 19 | update_all() 20 | -------------------------------------------------------------------------------- /maltose/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | 5 | def execute(): 6 | os.environ.setdefault('DJANGO_DEBUG', 'True') 7 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'maltose.maltose.settings') 8 | try: 9 | from django.core.management import execute_from_command_line 10 | except ImportError as exc: 11 | raise ImportError( 12 | "Couldn't import Django. Are you sure it's installed and " 13 | "available on your PYTHONPATH environment variable? Did you " 14 | "forget to activate a virtual environment?" 15 | ) from exc 16 | execute_from_command_line() 17 | -------------------------------------------------------------------------------- /maltose/editor/static/css/main.84c36446.chunk.css: -------------------------------------------------------------------------------- 1 | body,html{height:100%}body{margin:0;padding:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}code{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}#root{height:100%}.ant-btn[disabled]{background-color:#fff!important}button.ant-btn-icon-only{border:none}.ant-list-item-content{word-break:break-all}.ant-list-item-action{margin-left:15px!important}.ant-list-item-action>li:last-child{padding-right:0} 2 | /*# sourceMappingURL=main.84c36446.chunk.css.map */ -------------------------------------------------------------------------------- /maltose/sundries/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.5 on 2019-04-19 11:11 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='FriendLink', 16 | fields=[ 17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('name', models.CharField(max_length=50, verbose_name='名字')), 19 | ('link', models.URLField(verbose_name='网址')), 20 | ], 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /maltose/article/forms.py: -------------------------------------------------------------------------------- 1 | from django.forms import ModelForm 2 | 3 | from .models import * 4 | 5 | 6 | class TagForm(ModelForm): 7 | class Meta: 8 | model = Tag 9 | fields = '__all__' 10 | 11 | 12 | class CorpusForm(ModelForm): 13 | class Meta: 14 | model = Corpus 15 | fields = '__all__' 16 | 17 | 18 | class ArticleForm(ModelForm): 19 | class Meta: 20 | model = Article 21 | fields = '__all__' 22 | 23 | 24 | class ReferenceForm(ModelForm): 25 | class Meta: 26 | model = Reference 27 | fields = '__all__' 28 | 29 | 30 | class ImageForm(ModelForm): 31 | class Meta: 32 | model = Image 33 | fields = '__all__' 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Maltose 2 | 3 | 一个使用Django编写的静态博客生成器。 4 | 5 | ![](example.png) 6 | 7 | 拥有如下功能 8 | 9 | - [x] 内置图片管理 10 | - [x] Makrdown编辑器 in web 11 | 12 | ## 快速开始 13 | 14 | ``` 15 | pip install maltose 16 | ``` 17 | 18 | ### 使用 19 | 20 | 在你的博客目录下运行命令`maltose-cli migrate`用以创建数据库,如果目录下没有`maltose.json`配置,会询问你之后自动创建。 21 | 22 | 博客目录下必须存在`templates`,其中包含`articles`与`static`,前者包含所有页面的模板,后者包含所需要的静态文件。(可以使用现成的模板, 例如: https://github.com/MaltoseEditor/black-white ) 23 | 24 | 使用`maltose-cli.py runserver`,你将能启动Django程序,你可以在`localhost:8000/editor/`中获得一个编辑器,用以书写博客。程序使用Django信号来创建用以发布到网络上的静态页面。 25 | 26 | ### 高级使用 27 | 28 | 你可以直接下载源码然后依照标准Django程序进行修改,使用`python setup.py install`可以安装你自己改修后的版本。 29 | 30 | ## 更多说明 31 | 32 | ……to be continue 33 | -------------------------------------------------------------------------------- /maltose/maltose/common.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | 3 | from django.conf import settings 4 | 5 | 6 | def create_dict(local=None, field=None, **kwargs): 7 | """ 8 | 以字典的形式从局部变量locals()中获取指定的变量 9 | 10 | :param local: dict 11 | :param field: str[] 指定需要从local中读取的变量名称 12 | :param kwargs: 需要将变量指定额外名称时使用 13 | :return: dict 14 | """ 15 | if field is None or local is None: 16 | return {} 17 | result = {k: v for k, v in local.items() if k in field} 18 | result.update(**kwargs) 19 | return result 20 | 21 | 22 | def push(): 23 | """推送生成的静态页面到远程仓库""" 24 | return subprocess.Popen('git pull && git add . && git commit -m "Auto commit by Maltose" && git push', cwd=settings.BLOG_REPOSITORIES, shell=True) 25 | -------------------------------------------------------------------------------- /maltose/article/management/commands/update.py: -------------------------------------------------------------------------------- 1 | import os 2 | from django.core.management.base import BaseCommand, CommandError 3 | 4 | from maltose.article.models import Article 5 | from maltose.article.build import update_all, update_o 6 | 7 | 8 | class Command(BaseCommand): 9 | help = '更新或创建对应文章' 10 | 11 | def add_arguments(self, parser): 12 | os.environ['DJANGO_DEBUG'] = 'False' 13 | parser.add_argument('--all', action='store_true', dest="update-all") 14 | parser.add_argument('-o', type=str, dest="view") 15 | 16 | def handle(self, *args, **options): 17 | if options['update-all']: 18 | update_all() 19 | return 20 | if options.get('view') is not None: 21 | update_o(options['view']) 22 | return 23 | -------------------------------------------------------------------------------- /maltose/article/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import * 4 | 5 | 6 | class ReferenceAdmin(admin.TabularInline): 7 | model = Reference 8 | 9 | 10 | class ImageAdmin(admin.TabularInline): 11 | model = Image 12 | 13 | 14 | @admin.register(Article) 15 | class ArticleAdmin(admin.ModelAdmin): 16 | inlines = [ReferenceAdmin, ImageAdmin] 17 | list_display = ('title', 'create_time', 'update_time', 'is_draft', 'is_public') 18 | # list_editable = ('is_draft', 'is_public') 19 | fields = ('title', 'slug', 'corpus', 'tags') 20 | filter_horizontal = ('tags',) 21 | list_filter = ('is_draft', 'is_public') 22 | 23 | 24 | @admin.register(Tag) 25 | class TagAdmin(admin.ModelAdmin): 26 | pass 27 | 28 | 29 | @admin.register(Corpus) 30 | class CorpusAdmin(admin.ModelAdmin): 31 | pass 32 | -------------------------------------------------------------------------------- /maltose/maltose/management/commands/clear_migrations.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | from django.core.management.base import BaseCommand, CommandError 4 | from django.conf import settings 5 | 6 | 7 | class Command(BaseCommand): 8 | help = '清除项目路径下所有migrations下的文件' 9 | 10 | def handle(self, *args, **options): 11 | for app in settings.INSTALLED_APPS: 12 | path = os.path.join(os.path.join(settings.BASE_DIR, app.replace(".", "/")), "migrations") 13 | if os.path.exists(path): 14 | shutil.rmtree(path) 15 | os.makedirs(path) 16 | with open(os.path.join(path, "__init__.py"), "w+") as file: 17 | pass 18 | self.stdout.write(self.style.SUCCESS(f"Clear {path}")) 19 | self.stdout.write(self.style.SUCCESS('Successfully cleared!')) 20 | -------------------------------------------------------------------------------- /maltose/editor/precache-manifest.f7019b14d5f5d95e64545166a760a11b.js: -------------------------------------------------------------------------------- 1 | self.__precacheManifest = [ 2 | { 3 | "revision": "e21c45467fc6af0a4658", 4 | "url": "./static/js/runtime~main.e21c4546.js" 5 | }, 6 | { 7 | "revision": "d4aa6365bd00077fbf00", 8 | "url": "./static/js/main.d4aa6365.chunk.js" 9 | }, 10 | { 11 | "revision": "1e870795d465c3c421d0", 12 | "url": "./static/js/2.1e870795.chunk.js" 13 | }, 14 | { 15 | "revision": "75117139a78718858a4d", 16 | "url": "./static/js/1.75117139.chunk.js" 17 | }, 18 | { 19 | "revision": "d4aa6365bd00077fbf00", 20 | "url": "./static/css/main.84c36446.chunk.css" 21 | }, 22 | { 23 | "revision": "1e870795d465c3c421d0", 24 | "url": "./static/css/2.15984d9f.chunk.css" 25 | }, 26 | { 27 | "revision": "3d4563b2a3e6b3a3801e03a762e7d4d0", 28 | "url": "./index.html" 29 | } 30 | ]; -------------------------------------------------------------------------------- /maltose/editor/asset-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "main.css": "./static/css/main.84c36446.chunk.css", 3 | "main.js": "./static/js/main.d4aa6365.chunk.js", 4 | "main.js.map": "./static/js/main.d4aa6365.chunk.js.map", 5 | "static/js/1.75117139.chunk.js": "./static/js/1.75117139.chunk.js", 6 | "static/js/1.75117139.chunk.js.map": "./static/js/1.75117139.chunk.js.map", 7 | "static/css/2.15984d9f.chunk.css": "./static/css/2.15984d9f.chunk.css", 8 | "static/js/2.1e870795.chunk.js": "./static/js/2.1e870795.chunk.js", 9 | "static/js/2.1e870795.chunk.js.map": "./static/js/2.1e870795.chunk.js.map", 10 | "runtime~main.js": "./static/js/runtime~main.e21c4546.js", 11 | "runtime~main.js.map": "./static/js/runtime~main.e21c4546.js.map", 12 | "static/css/2.15984d9f.chunk.css.map": "./static/css/2.15984d9f.chunk.css.map", 13 | "static/css/main.84c36446.chunk.css.map": "./static/css/main.84c36446.chunk.css.map", 14 | "index.html": "./index.html", 15 | "precache-manifest.f7019b14d5f5d95e64545166a760a11b.js": "./precache-manifest.f7019b14d5f5d95e64545166a760a11b.js", 16 | "service-worker.js": "./service-worker.js" 17 | } -------------------------------------------------------------------------------- /maltose/article/context_processors.py: -------------------------------------------------------------------------------- 1 | from django.db.models import Count, F 2 | from django.db.models.functions.datetime import TruncMonth 3 | from django.conf import settings 4 | 5 | from .models import Corpus, Tag, Article 6 | 7 | 8 | def get_all_corpus(request): 9 | corpuses = Article.all().annotate(name=F('corpus__name')).values("name").distinct(). \ 10 | annotate(count=Count('id')).values('name', 'count').order_by().filter(corpus__name__isnull=False) 11 | return {"corpuses": corpuses} 12 | 13 | 14 | def get_all_tag(request): 15 | tags = Article.all().annotate(name=F('tags__name')).values("name").distinct(). \ 16 | annotate(count=Count('id')).values('name', 'count').order_by().filter(tags__isnull=False) 17 | return {"tags": tags} 18 | 19 | 20 | def get_all_timelist(request): 21 | # 这里为啥要先用一个order_by() 22 | # 详见 https://stackoverflow.com/questions/45630801/django-orm-queryset-group-by-month-week-truncmonth 23 | time_list = Article.all().annotate(date=TruncMonth('create_time')).values('date').distinct() \ 24 | .annotate(count=Count('id')).values('date', 'count').order_by().order_by('-date') 25 | return {"time_list": time_list} 26 | -------------------------------------------------------------------------------- /maltose/article/signals.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from django.db.models.signals import pre_delete, post_save, post_init 4 | from django.dispatch import receiver 5 | from django.conf import settings 6 | 7 | from .models import Article, Image 8 | from .build import update_all, update_feed, update_home, update_sitemap 9 | from .build import update_article as update_a 10 | 11 | 12 | @receiver(pre_delete, sender=Image) 13 | def del_image(sender, instance, **kwargs): 14 | if str(instance.file): 15 | image = os.path.join(settings.MEDIA_ROOT, str(instance.file)) 16 | if os.path.exists(image): 17 | os.remove(image) 18 | 19 | 20 | @receiver(post_init, sender=Article) 21 | def init_article(sender, instance, **kwargs): 22 | instance.onlychange_content = False 23 | 24 | 25 | @receiver(post_save, sender=Article) 26 | def update_article(sender, instance, **kwargs): 27 | if instance.onlychange_content: 28 | update_a(instance) 29 | update_home() 30 | update_feed() 31 | update_sitemap() 32 | else: 33 | update_all() 34 | 35 | 36 | @receiver(pre_delete, sender=Article) 37 | def delete_article(sender, instance, **kwargs): 38 | update_all() 39 | -------------------------------------------------------------------------------- /maltose/editor/service-worker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Welcome to your Workbox-powered service worker! 3 | * 4 | * You'll need to register this file in your web app and you should 5 | * disable HTTP caching for this file too. 6 | * See https://goo.gl/nhQhGp 7 | * 8 | * The rest of the code is auto-generated. Please don't update this file 9 | * directly; instead, make changes to your Workbox build configuration 10 | * and re-run your build process. 11 | * See https://goo.gl/2aRDsh 12 | */ 13 | 14 | importScripts("https://storage.googleapis.com/workbox-cdn/releases/3.6.3/workbox-sw.js"); 15 | 16 | importScripts( 17 | "./precache-manifest.f7019b14d5f5d95e64545166a760a11b.js" 18 | ); 19 | 20 | workbox.clientsClaim(); 21 | 22 | /** 23 | * The workboxSW.precacheAndRoute() method efficiently caches and responds to 24 | * requests for URLs in the manifest. 25 | * See https://goo.gl/S9QRab 26 | */ 27 | self.__precacheManifest = [].concat(self.__precacheManifest || []); 28 | workbox.precaching.suppressWarnings(); 29 | workbox.precaching.precacheAndRoute(self.__precacheManifest, {}); 30 | 31 | workbox.routing.registerNavigationRoute("./index.html", { 32 | 33 | blacklist: [/^\/_/,/\/[^\/]+\.[^\/]+$/], 34 | }); 35 | -------------------------------------------------------------------------------- /maltose/maltose/views.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import hmac 3 | from hashlib import sha1 4 | 5 | from django.shortcuts import render 6 | from django.http import JsonResponse, HttpResponseForbidden 7 | from django.views.decorators.csrf import csrf_exempt 8 | from django.conf import settings 9 | 10 | from .common import push as func_push 11 | 12 | 13 | def push(request): 14 | func_push() 15 | return JsonResponse({"message": "success"}) 16 | 17 | 18 | @csrf_exempt 19 | def coding_webhook(request): 20 | event = request.META.get("HTTP_X_CODING_EVENT") 21 | signature = request.META.get("HTTP_X_CODING_SIGNATURE", "").replace("sha1=", "") 22 | mac = hmac.new(settings.WEBHOOK_TOKEN.encode("ASCII"), msg=request.body, digestmod=sha1) 23 | if not hmac.compare_digest(str(mac.hexdigest()), str(signature)): 24 | return HttpResponseForbidden("What do you want to do?") 25 | if event == "ping": 26 | return JsonResponse({"message": "pong"}) 27 | elif event == "push": 28 | subprocess.Popen("git pull", cwd=settings.BASE_DIR, shell=True) 29 | return JsonResponse({"message": "success"}) 30 | 31 | 32 | @csrf_exempt 33 | def github_webhook(request): 34 | # TODO 完成Github-Webhook 35 | return HttpResponseForbidden("What do you want to do?") 36 | -------------------------------------------------------------------------------- /maltose/article/tests.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from django.test import TestCase 3 | from django.urls import reverse 4 | from django.contrib.auth import get_user_model 5 | 6 | from .models import Article 7 | 8 | 9 | class ModelTests(TestCase): 10 | 11 | def test_model_to_dict(self): 12 | """ 13 | 测试模型的to_dict方法 14 | """ 15 | article = Article( 16 | title="test-title", 17 | slug="test-slug", 18 | source='#source', 19 | ) 20 | article.save() 21 | self.assertDictEqual(article.to_dict(fields=['title']), {"title": 'test-title'}) 22 | self.assertIsInstance(article.to_dict()['create_time'], str) 23 | self.assertListEqual(article.to_dict(WTF=[{"1": 'test'}, {'2': 'test'}])['WTF'], [{"1": 'test'}, {'2': 'test'}]) 24 | self.assertDictEqual(article.to_dict(exclude=['id', 'create_time', 'update_time']), 25 | {'title': 'test-title', 'slug': 'test-slug', 'source': '#source', 26 | 'body': '', 'is_draft': True, 'is_public': True, }) 27 | 28 | def test_api(self): 29 | # 创建用户并登陆 30 | get_user_model().objects.create_superuser('Test', 'Test@qq.com', 'Test') 31 | self.assertIs(self.client.login(username='Test', password='Test'), True) 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | venv/ 83 | ENV/ 84 | .venv/ 85 | 86 | # Spyder project settings 87 | .spyderproject 88 | 89 | # Rope project settings 90 | .ropeproject 91 | *.npy 92 | *.pkl 93 | 94 | .idea/ 95 | 96 | .vscode/ 97 | 98 | *.sqlite3 99 | -------------------------------------------------------------------------------- /maltose/article/management/commands/import.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | from django.core.management.base import BaseCommand 4 | import markdown 5 | 6 | from article.models import Article, Tag, Corpus 7 | 8 | # TODO 导入文章 9 | ''' 10 | class Command(BaseCommand): 11 | help = '从指定目录中导入博文' 12 | 13 | def add_arguments(self, parser): 14 | parser.add_argument('path', type=str) 15 | 16 | def get_info(md: str) -> (dict, str): 17 | datas = re.match(r'---(?P[\S\s]+?)---', md).group('datas') 18 | 19 | info = dict() 20 | # XML风格 21 | if "" in datas: 22 | for each in re.findall(r'<(?P<name>\S+?)>([\S\s]*?)</\1>', datas): 23 | info[each[0]] = each[1].strip() 24 | 25 | if not info.get("tags"): 26 | info["tags"] = [] 27 | else: 28 | info["tags"] = info["tags"].split(",") 29 | 30 | # YAML风格 31 | else: 32 | _md = markdown.Markdown(extensions=['markdown.extensions.meta']) 33 | _md.convert(md) 34 | info = _md.Meta 35 | for key, value in info.items(): 36 | if key == "tags": 37 | continue 38 | info[key] = value[0] 39 | 40 | if info.get("tags") is None or info["tags"][0] == "": 41 | info["tags"] = [] 42 | 43 | md = re.sub(r"^---[\s\S]+?---", "", md) 44 | return info, md 45 | 46 | def handle(self, *args, **options): 47 | path = options["path"] 48 | for eachfile in os.listdir(path): 49 | if not eachfile.endswith('md'): 50 | continue 51 | with open(os.path.join(path, eachfile)) as file: 52 | data = file.read() 53 | info, md = self.get_info(data) 54 | ''' 55 | -------------------------------------------------------------------------------- /maltose/maltose/urls.py: -------------------------------------------------------------------------------- 1 | """maltose URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/2.1/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | import re 17 | from django.contrib import admin 18 | from django.urls import path, include 19 | from django.conf import settings 20 | from django.urls import re_path 21 | from django.conf.urls.static import static 22 | from django.views.static import serve 23 | from django.contrib.auth.decorators import login_required 24 | 25 | from .views import coding_webhook, github_webhook, push 26 | 27 | admin.site.site_header = 'Maltose | 后台管理' 28 | admin.site.site_title = 'Maltose' 29 | admin.site.index_title = 'Maltose' 30 | 31 | urlpatterns = [ 32 | path('admin/', admin.site.urls), 33 | path('coding-webhook', coding_webhook), 34 | path('github-webhook', github_webhook), 35 | path('push', push), 36 | path('accounts/', include('django.contrib.auth.urls')), 37 | # 后台界面 38 | path('editor/', serve, kwargs={"path": 'index.html', "document_root": settings.EDITOR_ROOT}), 39 | re_path(r'^editor/(?P<path>.*)$', serve, kwargs={"document_root": settings.EDITOR_ROOT}), 40 | 41 | path('', include('maltose.article.urls')), 42 | ] 43 | 44 | urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 45 | 46 | if not settings.DEBUG: 47 | urlpatterns += [ 48 | re_path(r'^%s(?P<path>.*)$' % re.escape(settings.STATIC_URL.lstrip('/')), serve, 49 | kwargs={"document_root": settings.STATICFILES_DIRS[0]}), 50 | re_path(r'^%s(?P<path>.*)$' % re.escape(settings.MEDIA_URL.lstrip('/')), serve, 51 | kwargs={"document_root": settings.MEDIA_ROOT}), 52 | ] 53 | -------------------------------------------------------------------------------- /maltose/editor/static/css/main.84c36446.chunk.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["/mnt/c/Users/AberS/Documents/Github/MaltoseEditor/editor/src/index.css","main.84c36446.chunk.css","/mnt/c/Users/AberS/Documents/Github/MaltoseEditor/editor/src/view/App.css"],"names":[],"mappings":"AAAA,UACE,WCCF,CDEA,KACE,QAAA,CACA,SAAA,CACA,mIAAA,CACA,kCAAA,CACA,iCCCF,CDEA,KACE,uECCF,CCdA,MACE,WDgBF,CCbA,mBAIE,+BDgBF,CCbA,yBAIE,WDgBF,CCbA,uBAIE,oBDgBF,CCbA,sBAIE,0BDgBF,CCbA,oCAIE,eDgBF","file":"main.84c36446.chunk.css","sourcesContent":["html, body {\n height: 100%;\n}\n\nbody {\n margin: 0;\n padding: 0;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", \"Roboto\", \"Oxygen\", \"Ubuntu\", \"Cantarell\", \"Fira Sans\", \"Droid Sans\", \"Helvetica Neue\", sans-serif;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\ncode {\n font-family: source-code-pro, Menlo, Monaco, Consolas, \"Courier New\", monospace;\n}","html, body {\n height: 100%;\n}\n\nbody {\n margin: 0;\n padding: 0;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", \"Roboto\", \"Oxygen\", \"Ubuntu\", \"Cantarell\", \"Fira Sans\", \"Droid Sans\", \"Helvetica Neue\", sans-serif;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\ncode {\n font-family: source-code-pro, Menlo, Monaco, Consolas, \"Courier New\", monospace;\n}\n#root {\n height: 100%;\n}\n\n.ant-btn[disabled] {\n /*\n 清除不可用图标的淡灰色背景\n */\n background-color: #FFF !important;\n}\n\nbutton.ant-btn-icon-only {\n /*\n 清除图标按钮的边框\n */\n border: none;\n}\n\n.ant-list-item-content {\n /*\n 强制断词换行\n */\n word-break:break-all;\n}\n\n.ant-list-item-action {\n /*\n 清除action左边距\n */\n margin-left: 15px !important;\n}\n\n.ant-list-item-action>li:last-child {\n /*\n 清除action最后一个操作的右内边距\n */\n padding-right: 0;\n}\n","#root {\n height: 100%;\n}\n\n.ant-btn[disabled] {\n /*\n 清除不可用图标的淡灰色背景\n */\n background-color: #FFF !important;\n}\n\nbutton.ant-btn-icon-only {\n /*\n 清除图标按钮的边框\n */\n border: none;\n}\n\n.ant-list-item-content {\n /*\n 强制断词换行\n */\n word-break:break-all;\n}\n\n.ant-list-item-action {\n /*\n 清除action左边距\n */\n margin-left: 15px !important;\n}\n\n.ant-list-item-action>li:last-child {\n /*\n 清除action最后一个操作的右内边距\n */\n padding-right: 0;\n}"]} -------------------------------------------------------------------------------- /maltose/editor/static/js/runtime~main.e21c4546.js: -------------------------------------------------------------------------------- 1 | !function(e){function t(t){for(var n,i,a=t[0],c=t[1],l=t[2],p=0,s=[];p<a.length;p++)i=a[p],o[i]&&s.push(o[i][0]),o[i]=0;for(n in c)Object.prototype.hasOwnProperty.call(c,n)&&(e[n]=c[n]);for(f&&f(t);s.length;)s.shift()();return u.push.apply(u,l||[]),r()}function r(){for(var e,t=0;t<u.length;t++){for(var r=u[t],n=!0,a=1;a<r.length;a++){var c=r[a];0!==o[c]&&(n=!1)}n&&(u.splice(t--,1),e=i(i.s=r[0]))}return e}var n={},o={3:0},u=[];function i(t){if(n[t])return n[t].exports;var r=n[t]={i:t,l:!1,exports:{}};return e[t].call(r.exports,r,r.exports,i),r.l=!0,r.exports}i.e=function(e){var t=[],r=o[e];if(0!==r)if(r)t.push(r[2]);else{var n=new Promise(function(t,n){r=o[e]=[t,n]});t.push(r[2]=n);var u,a=document.getElementsByTagName("head")[0],c=document.createElement("script");c.charset="utf-8",c.timeout=120,i.nc&&c.setAttribute("nonce",i.nc),c.src=function(e){return i.p+"static/js/"+({}[e]||e)+"."+{1:"75117139"}[e]+".chunk.js"}(e),u=function(t){c.onerror=c.onload=null,clearTimeout(l);var r=o[e];if(0!==r){if(r){var n=t&&("load"===t.type?"missing":t.type),u=t&&t.target&&t.target.src,i=new Error("Loading chunk "+e+" failed.\n("+n+": "+u+")");i.type=n,i.request=u,r[1](i)}o[e]=void 0}};var l=setTimeout(function(){u({type:"timeout",target:c})},12e4);c.onerror=c.onload=u,a.appendChild(c)}return Promise.all(t)},i.m=e,i.c=n,i.d=function(e,t,r){i.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},i.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,t){if(1&t&&(e=i(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(i.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var n in e)i.d(r,n,function(t){return e[t]}.bind(null,n));return r},i.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(t,"a",t),t},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.p="./",i.oe=function(e){throw console.error(e),e};var a=window.webpackJsonp=window.webpackJsonp||[],c=a.push.bind(a);a.push=t,a=a.slice();for(var l=0;l<a.length;l++)t(a[l]);var f=c;r()}([]); 2 | //# sourceMappingURL=runtime~main.e21c4546.js.map -------------------------------------------------------------------------------- /maltose/article/urls.py: -------------------------------------------------------------------------------- 1 | """maltose URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/2.1/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.urls import path, re_path 17 | from django.conf import settings 18 | from django.contrib.sitemaps.views import sitemap 19 | from django.views.static import serve as _serve 20 | 21 | from .views import * 22 | 23 | 24 | def serve(request, path): 25 | if path == "" or path[-1] == "/": 26 | path += 'index.html' 27 | return _serve(request, path, settings.BLOG_REPOSITORIES) 28 | 29 | 30 | app_name = 'article' 31 | 32 | urlpatterns = [ 33 | path('', home, name='get_home'), 34 | path('articles/<str:slug>/', get_article, name="get_article"), 35 | path('tags/<str:name>/', get_tag, name="get_tag"), 36 | path('corpus/<str:name>/', get_corpus, name="get_corpus"), 37 | path('time/<int:year>/<int:month>/', get_time, name='get_time'), 38 | path('about/', about, name="get_about"), 39 | path('about/feedback.html', feedback, name="get_feedback"), 40 | path('about/donation.html', donation, name="get_donation"), 41 | path('404.html', page_not_found, name="get_404"), 42 | path('sitemap.xml', sitemap, kwargs={'sitemaps': {"article": Sitemap}}, name="get_sitemap"), 43 | path('feed.xml', Feed(), name="get_feed"), 44 | # 模型操作Api 45 | path('api/article/', ArticleView.as_view(), name='api_article'), 46 | path('api/tag/', TagView.as_view(), name='api_tag'), 47 | path('api/corpus/', CorpusView.as_view(), name='api_corpus'), 48 | path('api/reference/', ReferenceView.as_view(), name='api_reference'), 49 | path('api/image/', ImageView.as_view(), name='api_image'), 50 | # 使用Python渲染的Api 51 | path('api/render/', RenderView.as_view(), name="api_render"), 52 | 53 | # 在其他url规则无法匹配到时, 自动调用此函数模拟Github Page 54 | re_path(r'^(?P<path>.*)$', serve), 55 | ] 56 | -------------------------------------------------------------------------------- /maltose/article/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.conf import settings 3 | from django.urls import reverse 4 | 5 | from maltose.maltose.models import ModelSerializationMixin 6 | 7 | __all__ = ( 8 | "Tag", 'Corpus', 'Article', 'Reference', 'Image' 9 | ) 10 | 11 | 12 | class Tag(models.Model, ModelSerializationMixin): 13 | name = models.CharField("标签名", max_length=20, unique=True) 14 | 15 | def __str__(self): 16 | return self.name 17 | 18 | def get_absolute_url(self): 19 | return f'/tags/{self.name}/' 20 | 21 | 22 | class Corpus(models.Model, ModelSerializationMixin): 23 | name = models.CharField("文集名", max_length=20, unique=True) 24 | 25 | def __str__(self): 26 | return self.name 27 | 28 | def get_absolute_url(self): 29 | return f'/corpus/{self.name}/' 30 | 31 | 32 | class Article(models.Model, ModelSerializationMixin): 33 | title = models.CharField("标题", max_length=25, unique=True) 34 | slug = models.SlugField("自定义链接", unique=True) 35 | create_time = models.DateTimeField("创建时间", auto_now_add=True) 36 | update_time = models.DateTimeField("更新时间", auto_now=True) 37 | # 由前端进行Markdown渲染, 能保证显示的及时性并减少服务器压力 38 | # source存在的意义仅为储存原始的markdown内容, 页面中应直接使用body 39 | source = models.TextField("Markdown", blank=True) 40 | body = models.TextField("文章内容", blank=True) 41 | tags = models.ManyToManyField(Tag, blank=True) 42 | corpus = models.ForeignKey(Corpus, on_delete=models.SET_NULL, blank=True, null=True) 43 | is_draft = models.BooleanField("暂不发表", default=True) 44 | is_public = models.BooleanField("全部公开", default=True) 45 | 46 | @staticmethod 47 | def all(): 48 | return Article.objects.filter(is_public=True, is_draft=False) 49 | 50 | def get_absolute_url(self): 51 | return reverse('article:get_article', kwargs={"slug": self.slug}) 52 | 53 | class Meta: 54 | ordering = ['-create_time'] 55 | 56 | def __str__(self): 57 | return self.title 58 | 59 | 60 | class Reference(models.Model, ModelSerializationMixin): 61 | name = models.CharField("名称", max_length=50) 62 | link = models.URLField("链接") 63 | article = models.ForeignKey(Article, on_delete=models.CASCADE) 64 | 65 | def __str__(self): 66 | return self.link 67 | 68 | 69 | class Image(models.Model, ModelSerializationMixin): 70 | file = models.ImageField("路径", upload_to='') 71 | article = models.ForeignKey(Article, on_delete=models.CASCADE) 72 | 73 | def __str__(self): 74 | return str(self.file) 75 | -------------------------------------------------------------------------------- /maltose/maltose/templates/registration/login.html: -------------------------------------------------------------------------------- 1 | <!DOCTYPE html> 2 | <html lang="zh-cn"> 3 | 4 | <head> 5 | <meta charset="utf-8"> 6 | <meta http-equiv="x-ua-compatible" content="ie=edge"> 7 | <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> 8 | <title>登录 9 | 11 | 16 | 17 | 18 | 19 | 20 | 21 | 32 | 33 | 34 | 35 |
36 |
37 |
38 |
39 | {{ form.non_field_errors }} 40 | {% csrf_token %} 41 | {% for field in form %} 42 |
43 | {{ field.label_tag }} 44 | {{ field }} 45 | {{ field.errors }} 46 | {% if field.help_text %} 47 | 48 | {{ field.help_text | safe }} 49 | 50 | {% endif %} 51 |
52 | {% endfor %} 53 |
54 | 55 |
56 |
57 |
58 |
59 | 60 | 61 | -------------------------------------------------------------------------------- /maltose/editor/index.html: -------------------------------------------------------------------------------- 1 | Maltose | Admin
-------------------------------------------------------------------------------- /maltose/editor/static/js/1.75117139.chunk.js: -------------------------------------------------------------------------------- 1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[1],{434:function(e,t,n){"use strict";n.r(t),n.d(t,"conf",function(){return s}),n.d(t,"language",function(){return o});var s={comments:{blockComment:["\x3c!--","--\x3e"]},brackets:[["{","}"],["[","]"],["(",")"]],autoClosingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:"<",close:">",notIn:["string"]}],surroundingPairs:[{open:"(",close:")"},{open:"[",close:"]"},{open:"`",close:"`"}],folding:{markers:{start:new RegExp("^\\s*\x3c!--\\s*#?region\\b.*--\x3e"),end:new RegExp("^\\s*\x3c!--\\s*#?endregion\\b.*--\x3e")}}},o={defaultToken:"",tokenPostfix:".md",control:/[\\`*_\[\]{}()#+\-\.!]/,noncontrol:/[^\\`*_\[\]{}()#+\-\.!]/,escapes:/\\(?:@control)/,jsescapes:/\\(?:[btnfr\\"']|[0-7][0-7]?|[0-3][0-7]{2})/,empty:["area","base","basefont","br","col","frame","hr","img","input","isindex","link","meta","param"],tokenizer:{root:[[/^(\s{0,3})(#+)((?:[^\\#]|@escapes)+)((?:#+)?)/,["white","keyword","keyword","keyword"]],[/^\s*(=+|\-+)\s*$/,"keyword"],[/^\s*((\*[ ]?)+)\s*$/,"meta.separator"],[/^\s*>+/,"comment"],[/^\s*([\*\-+:]|\d+\.)\s/,"keyword"],[/^(\t|[ ]{4})[^ ].*$/,"string"],[/^\s*~~~\s*((?:\w|[\/\-#])+)?\s*$/,{token:"string",next:"@codeblock"}],[/^\s*```\s*((?:\w|[\/\-#])+)\s*$/,{token:"string",next:"@codeblockgh",nextEmbedded:"$1"}],[/^\s*```\s*$/,{token:"string",next:"@codeblock"}],{include:"@linecontent"}],codeblock:[[/^\s*~~~\s*$/,{token:"string",next:"@pop"}],[/^\s*```\s*$/,{token:"string",next:"@pop"}],[/.*$/,"variable.source"]],codeblockgh:[[/```\s*$/,{token:"variable.source",next:"@pop",nextEmbedded:"@pop"}],[/[^`]+/,"variable.source"]],linecontent:[[/&\w+;/,"string.escape"],[/@escapes/,"escape"],[/\b__([^\\_]|@escapes|_(?!_))+__\b/,"strong"],[/\*\*([^\\*]|@escapes|\*(?!\*))+\*\*/,"strong"],[/\b_[^_]+_\b/,"emphasis"],[/\*([^\\*]|@escapes)+\*/,"emphasis"],[/`([^\\`]|@escapes)+`/,"variable"],[/\{+[^}]+\}+/,"string.target"],[/(!?\[)((?:[^\]\\]|@escapes)*)(\]\([^\)]+\))/,["string.link","","string.link"]],[/(!?\[)((?:[^\]\\]|@escapes)*)(\])/,"string.link"],{include:"html"}],html:[[/<(\w+)\/>/,"tag"],[/<(\w+)/,{cases:{"@empty":{token:"tag",next:"@tag.$1"},"@default":{token:"tag",next:"@tag.$1"}}}],[/<\/(\w+)\s*>/,{token:"tag"}],[//,"comment","@pop"],[/']\n },\n brackets: [['{', '}'], ['[', ']'], ['(', ')']],\n autoClosingPairs: [{\n open: '{',\n close: '}'\n }, {\n open: '[',\n close: ']'\n }, {\n open: '(',\n close: ')'\n }, {\n open: '<',\n close: '>',\n notIn: ['string']\n }],\n surroundingPairs: [{\n open: '(',\n close: ')'\n }, {\n open: '[',\n close: ']'\n }, {\n open: '`',\n close: '`'\n }],\n folding: {\n markers: {\n start: new RegExp(\"^\\\\s*\"),\n end: new RegExp(\"^\\\\s*\")\n }\n }\n};\nexport var language = {\n defaultToken: '',\n tokenPostfix: '.md',\n // escape codes\n control: /[\\\\`*_\\[\\]{}()#+\\-\\.!]/,\n noncontrol: /[^\\\\`*_\\[\\]{}()#+\\-\\.!]/,\n escapes: /\\\\(?:@control)/,\n // escape codes for javascript/CSS strings\n jsescapes: /\\\\(?:[btnfr\\\\\"']|[0-7][0-7]?|[0-3][0-7]{2})/,\n // non matched elements\n empty: ['area', 'base', 'basefont', 'br', 'col', 'frame', 'hr', 'img', 'input', 'isindex', 'link', 'meta', 'param'],\n tokenizer: {\n root: [// headers (with #)\n [/^(\\s{0,3})(#+)((?:[^\\\\#]|@escapes)+)((?:#+)?)/, ['white', 'keyword', 'keyword', 'keyword']], // headers (with =)\n [/^\\s*(=+|\\-+)\\s*$/, 'keyword'], // headers (with ***)\n [/^\\s*((\\*[ ]?)+)\\s*$/, 'meta.separator'], // quote\n [/^\\s*>+/, 'comment'], // list (starting with * or number)\n [/^\\s*([\\*\\-+:]|\\d+\\.)\\s/, 'keyword'], // code block (4 spaces indent)\n [/^(\\t|[ ]{4})[^ ].*$/, 'string'], // code block (3 tilde)\n [/^\\s*~~~\\s*((?:\\w|[\\/\\-#])+)?\\s*$/, {\n token: 'string',\n next: '@codeblock'\n }], // github style code blocks (with backticks and language)\n [/^\\s*```\\s*((?:\\w|[\\/\\-#])+)\\s*$/, {\n token: 'string',\n next: '@codeblockgh',\n nextEmbedded: '$1'\n }], // github style code blocks (with backticks but no language)\n [/^\\s*```\\s*$/, {\n token: 'string',\n next: '@codeblock'\n }], // markup within lines\n {\n include: '@linecontent'\n }],\n codeblock: [[/^\\s*~~~\\s*$/, {\n token: 'string',\n next: '@pop'\n }], [/^\\s*```\\s*$/, {\n token: 'string',\n next: '@pop'\n }], [/.*$/, 'variable.source']],\n // github style code blocks\n codeblockgh: [[/```\\s*$/, {\n token: 'variable.source',\n next: '@pop',\n nextEmbedded: '@pop'\n }], [/[^`]+/, 'variable.source']],\n linecontent: [// escapes\n [/&\\w+;/, 'string.escape'], [/@escapes/, 'escape'], // various markup\n [/\\b__([^\\\\_]|@escapes|_(?!_))+__\\b/, 'strong'], [/\\*\\*([^\\\\*]|@escapes|\\*(?!\\*))+\\*\\*/, 'strong'], [/\\b_[^_]+_\\b/, 'emphasis'], [/\\*([^\\\\*]|@escapes)+\\*/, 'emphasis'], [/`([^\\\\`]|@escapes)+`/, 'variable'], // links\n [/\\{+[^}]+\\}+/, 'string.target'], [/(!?\\[)((?:[^\\]\\\\]|@escapes)*)(\\]\\([^\\)]+\\))/, ['string.link', '', 'string.link']], [/(!?\\[)((?:[^\\]\\\\]|@escapes)*)(\\])/, 'string.link'], // or html\n {\n include: 'html'\n }],\n // Note: it is tempting to rather switch to the real HTML mode instead of building our own here\n // but currently there is a limitation in Monarch that prevents us from doing it: The opening\n // '<' would start the HTML mode, however there is no way to jump 1 character back to let the\n // HTML mode also tokenize the opening angle bracket. Thus, even though we could jump to HTML,\n // we cannot correctly tokenize it in that mode yet.\n html: [// html tags\n [/<(\\w+)\\/>/, 'tag'], [/<(\\w+)/, {\n cases: {\n '@empty': {\n token: 'tag',\n next: '@tag.$1'\n },\n '@default': {\n token: 'tag',\n next: '@tag.$1'\n }\n }\n }], [/<\\/(\\w+)\\s*>/, {\n token: 'tag'\n }], [//, 'comment', '@pop'], [/