├── 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 | 
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\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.*)$', 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.*)$' % re.escape(settings.STATIC_URL.lstrip('/')), serve,
49 | kwargs={"document_root": settings.STATICFILES_DIRS[0]}),
50 | re_path(r'^%s(?P.*)$' % 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/', get_article, name="get_article"),
35 | path('tags//', get_tag, name="get_tag"),
36 | path('corpus//', get_corpus, name="get_corpus"),
37 | path('time///', 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.*)$', 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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | 登录
9 |
11 |
16 |
17 |
18 |
19 |
20 |
21 |
32 |
33 |
34 |
35 |
36 |
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'], [/