mirror of
https://github.com/minetest/contentdb.git
synced 2025-01-09 06:27:38 +01:00
Add table of contents to help pages
This commit is contained in:
parent
5017a9ba7e
commit
14810b2cc5
@ -21,7 +21,7 @@ import flask_menu as menu
|
|||||||
from flask_mail import Mail
|
from flask_mail import Mail
|
||||||
from flask_github import GitHub
|
from flask_github import GitHub
|
||||||
from flask_wtf.csrf import CSRFProtect
|
from flask_wtf.csrf import CSRFProtect
|
||||||
from flask_flatpages import FlatPages, pygments_style_defs
|
from flask_flatpages import FlatPages
|
||||||
from flask_babel import Babel
|
from flask_babel import Babel
|
||||||
from flask_login import logout_user, current_user, LoginManager
|
from flask_login import logout_user, current_user, LoginManager
|
||||||
import os, redis
|
import os, redis
|
||||||
@ -29,7 +29,7 @@ import os, redis
|
|||||||
app = Flask(__name__, static_folder="public/static")
|
app = Flask(__name__, static_folder="public/static")
|
||||||
app.config["FLATPAGES_ROOT"] = "flatpages"
|
app.config["FLATPAGES_ROOT"] = "flatpages"
|
||||||
app.config["FLATPAGES_EXTENSION"] = ".md"
|
app.config["FLATPAGES_EXTENSION"] = ".md"
|
||||||
app.config["FLATPAGES_MARKDOWN_EXTENSIONS"] = ["fenced_code", "tables", "codehilite"]
|
app.config["FLATPAGES_MARKDOWN_EXTENSIONS"] = ["fenced_code", "tables", "codehilite", 'toc']
|
||||||
app.config["FLATPAGES_EXTENSION_CONFIG"] = {
|
app.config["FLATPAGES_EXTENSION_CONFIG"] = {
|
||||||
"fenced_code": {},
|
"fenced_code": {},
|
||||||
"tables": {},
|
"tables": {},
|
||||||
@ -69,7 +69,7 @@ if not app.debug and app.config["MAIL_UTILS_ERROR_SEND_TO"]:
|
|||||||
app.logger.addHandler(build_handler(app))
|
app.logger.addHandler(build_handler(app))
|
||||||
|
|
||||||
|
|
||||||
from .markdown import init_app
|
from app.utils.markdown import init_app
|
||||||
init_app(app)
|
init_app(app)
|
||||||
|
|
||||||
# @babel.localeselector
|
# @babel.localeselector
|
||||||
|
@ -21,7 +21,7 @@ from flask_wtf import FlaskForm
|
|||||||
from wtforms import *
|
from wtforms import *
|
||||||
from wtforms.validators import *
|
from wtforms.validators import *
|
||||||
|
|
||||||
from app.markdown import render_markdown
|
from app.utils.markdown import render_markdown
|
||||||
from app.models import *
|
from app.models import *
|
||||||
from app.tasks.emails import send_user_email
|
from app.tasks.emails import send_user_email
|
||||||
from app.utils import rank_required, addAuditLog
|
from app.utils import rank_required, addAuditLog
|
||||||
|
@ -19,8 +19,8 @@ from flask_login import current_user, login_required
|
|||||||
from sqlalchemy.sql.expression import func
|
from sqlalchemy.sql.expression import func
|
||||||
|
|
||||||
from app import csrf
|
from app import csrf
|
||||||
from app.markdown import render_markdown
|
from app.utils.markdown import render_markdown
|
||||||
from app.models import Tag, PackageState, PackageType, Package, db, PackageRelease, Tags, Permission, ForumTopic, MinetestRelease, APIToken, PackageScreenshot
|
from app.models import Tag, PackageState, PackageType, Package, db, PackageRelease, Permission, ForumTopic, MinetestRelease, APIToken, PackageScreenshot
|
||||||
from app.querybuilder import QueryBuilder
|
from app.querybuilder import QueryBuilder
|
||||||
from app.utils import is_package_page
|
from app.utils import is_package_page
|
||||||
from . import bp
|
from . import bp
|
||||||
|
@ -19,7 +19,7 @@ from flask import redirect, render_template, session, request, flash, url_for
|
|||||||
from app.models import db, User, UserRank
|
from app.models import db, User, UserRank
|
||||||
from app.utils import randomString, login_user_set_active
|
from app.utils import randomString, login_user_set_active
|
||||||
from app.tasks.forumtasks import checkForumAccount
|
from app.tasks.forumtasks import checkForumAccount
|
||||||
from app.tasks.phpbbparser import getProfile
|
from app.utils.phpbbparser import getProfile
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
title: Help
|
title: Help
|
||||||
|
toc: False
|
||||||
|
|
||||||
## General Help
|
## General Help
|
||||||
|
|
||||||
|
@ -11,16 +11,13 @@ curl -H "Authorization: Bearer YOURTOKEN" https://content.minetest.net/api/whoam
|
|||||||
|
|
||||||
Tokens can be attained by visiting [Settings > API Tokens](/user/tokens/).
|
Tokens can be attained by visiting [Settings > API Tokens](/user/tokens/).
|
||||||
|
|
||||||
## Endpoints
|
|
||||||
|
|
||||||
### Misc
|
|
||||||
|
|
||||||
* GET `/api/whoami/` - JSON dictionary with the following keys:
|
* GET `/api/whoami/` - JSON dictionary with the following keys:
|
||||||
* `is_authenticated` - True on successful API authentication
|
* `is_authenticated` - True on successful API authentication
|
||||||
* `username` - Username of the user authenticated as, null otherwise.
|
* `username` - Username of the user authenticated as, null otherwise.
|
||||||
* 4xx status codes will be thrown on unsupported authentication type, invalid access token, or other errors.
|
* 4xx status codes will be thrown on unsupported authentication type, invalid access token, or other errors.
|
||||||
|
|
||||||
### Packages
|
|
||||||
|
## Packages
|
||||||
|
|
||||||
* GET `/api/packages/` - See [Package Queries](#package-queries)
|
* GET `/api/packages/` - See [Package Queries](#package-queries)
|
||||||
* GET `/api/scores/` - See [Package Queries](#package-queries)
|
* GET `/api/scores/` - See [Package Queries](#package-queries)
|
||||||
@ -42,7 +39,31 @@ Tokens can be attained by visiting [Settings > API Tokens](/user/tokens/).
|
|||||||
* `high_reviewed` - highest reviewed
|
* `high_reviewed` - highest reviewed
|
||||||
* `tags`
|
* `tags`
|
||||||
|
|
||||||
### Releases
|
### Package Queries
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
/api/packages/?type=mod&type=game&q=mobs+fun&hide=nonfree&hide=gore
|
||||||
|
|
||||||
|
Supported query parameters:
|
||||||
|
|
||||||
|
* `type` - Package types (`mod`, `game`, `txp`).
|
||||||
|
* `q` - Query string.
|
||||||
|
* `author` - Filter by author.
|
||||||
|
* `tag` - Filter by tags.
|
||||||
|
* `random` - When present, enable random ordering and ignore `sort`.
|
||||||
|
* `limit` - Return at most `limit` packages.
|
||||||
|
* `hide` - Hide content based on [Content Flags](/help/content_flags/).
|
||||||
|
* `sort` - Sort by (`name`, `title`, `score`, `reviews`, `downloads`, `created_at`, `approved_at`, `last_release`).
|
||||||
|
* `order` - Sort ascending (`asc`) or descending (`desc`).
|
||||||
|
* `protocol_version` - Only show packages supported by this Minetest protocol version.
|
||||||
|
* `engine_version` - Only show packages supported by this Minetest engine version, eg: `5.3.0`.
|
||||||
|
* `fmt` - How the response is formated.
|
||||||
|
* `keys` - author/name only.
|
||||||
|
* `short` - stuff needed for the Minetest client.
|
||||||
|
|
||||||
|
|
||||||
|
## Releases
|
||||||
|
|
||||||
* GET `/api/packages/<username>/<name>/releases/` (List)
|
* GET `/api/packages/<username>/<name>/releases/` (List)
|
||||||
* Returns array of release dictionaries with keys:
|
* Returns array of release dictionaries with keys:
|
||||||
@ -87,7 +108,8 @@ curl -X DELETE https://content.minetest.net/api/packages/username/name/releases/
|
|||||||
-H "Authorization: Bearer YOURTOKEN"
|
-H "Authorization: Bearer YOURTOKEN"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Screenshots
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
* GET `/api/packages/<username>/<name>/screenshots/` (List)
|
* GET `/api/packages/<username>/<name>/screenshots/` (List)
|
||||||
* Returns array of screenshot dictionaries with keys:
|
* Returns array of screenshot dictionaries with keys:
|
||||||
@ -129,42 +151,14 @@ curl -X POST https://content.minetest.net/api/packages/username/name/screenshots
|
|||||||
-d "[13, 2, 5, 7]"
|
-d "[13, 2, 5, 7]"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Topics
|
|
||||||
|
## Topics
|
||||||
|
|
||||||
* GET `/api/topics/` - Supports [Package Queries](#package-queries), and the following two options:
|
* GET `/api/topics/` - Supports [Package Queries](#package-queries), and the following two options:
|
||||||
* `show_added` - Show topics which exist as packages, default true.
|
* `show_added` - Show topics which exist as packages, default true.
|
||||||
* `show_discarded` - Show topics which have been marked as outdated, default false.
|
* `show_discarded` - Show topics which have been marked as outdated, default false.
|
||||||
|
|
||||||
### Minetest
|
### Topic Queries
|
||||||
|
|
||||||
* GET `/api/minetest_versions/`
|
|
||||||
|
|
||||||
|
|
||||||
## Package Queries
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
/api/packages/?type=mod&type=game&q=mobs+fun&hide=nonfree&hide=gore
|
|
||||||
|
|
||||||
Supported query parameters:
|
|
||||||
|
|
||||||
* `type` - Package types (`mod`, `game`, `txp`).
|
|
||||||
* `q` - Query string.
|
|
||||||
* `author` - Filter by author.
|
|
||||||
* `tag` - Filter by tags.
|
|
||||||
* `random` - When present, enable random ordering and ignore `sort`.
|
|
||||||
* `limit` - Return at most `limit` packages.
|
|
||||||
* `hide` - Hide content based on [Content Flags](/help/content_flags/).
|
|
||||||
* `sort` - Sort by (`name`, `title`, `score`, `reviews`, `downloads`, `created_at`, `approved_at`, `last_release`).
|
|
||||||
* `order` - Sort ascending (`asc`) or descending (`desc`).
|
|
||||||
* `protocol_version` - Only show packages supported by this Minetest protocol version.
|
|
||||||
* `engine_version` - Only show packages supported by this Minetest engine version, eg: `5.3.0`.
|
|
||||||
* `fmt` - How the response is formated.
|
|
||||||
* `keys` - author/name only.
|
|
||||||
* `short` - stuff needed for the Minetest client.
|
|
||||||
|
|
||||||
|
|
||||||
## Topic Queries
|
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
@ -178,3 +172,8 @@ Supported query parameters:
|
|||||||
* `show_added` - Show topics that have an existing package.
|
* `show_added` - Show topics that have an existing package.
|
||||||
* `show_discarded` - Show topics marked as discarded.
|
* `show_discarded` - Show topics marked as discarded.
|
||||||
* `limit` - Return at most `limit` topics.
|
* `limit` - Return at most `limit` topics.
|
||||||
|
|
||||||
|
|
||||||
|
## Minetest
|
||||||
|
|
||||||
|
* GET `/api/minetest_versions/`
|
||||||
|
@ -148,3 +148,24 @@ blockquote {
|
|||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.toc {
|
||||||
|
.nav-link {
|
||||||
|
color: #ADADAD;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
|
||||||
|
&:hover, &.active {
|
||||||
|
color: #DDD;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav .nav {
|
||||||
|
margin: 0.1em 0 0.1em 0.7rem;
|
||||||
|
padding-left: 0.25em;
|
||||||
|
border-left: 3px solid rgba(173, 173, 173, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
& > .nav > * > .nav {
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
import json, re, sys
|
import json, re, sys
|
||||||
from app.models import *
|
from app.models import *
|
||||||
from app.tasks import celery
|
from app.tasks import celery
|
||||||
from .phpbbparser import getProfile, getTopicsFromForum
|
from app.utils.phpbbparser import getProfile, getTopicsFromForum
|
||||||
import urllib.request
|
import urllib.request
|
||||||
|
|
||||||
@celery.task()
|
@celery.task()
|
||||||
|
@ -6,6 +6,9 @@ from flask_babel import format_timedelta, gettext
|
|||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
from datetime import datetime as dt
|
from datetime import datetime as dt
|
||||||
|
|
||||||
|
from .utils.markdown import get_headings
|
||||||
|
|
||||||
|
|
||||||
@app.context_processor
|
@app.context_processor
|
||||||
def inject_debug():
|
def inject_debug():
|
||||||
return dict(debug=app.debug)
|
return dict(debug=app.debug)
|
||||||
@ -13,7 +16,9 @@ def inject_debug():
|
|||||||
@app.context_processor
|
@app.context_processor
|
||||||
def inject_functions():
|
def inject_functions():
|
||||||
check_global_perm = Permission.checkPerm
|
check_global_perm = Permission.checkPerm
|
||||||
return dict(abs_url_for=abs_url_for, url_set_query=url_set_query, check_global_perm=check_global_perm)
|
return dict(abs_url_for=abs_url_for, url_set_query=url_set_query,
|
||||||
|
check_global_perm=check_global_perm,
|
||||||
|
get_headings=get_headings)
|
||||||
|
|
||||||
@app.context_processor
|
@app.context_processor
|
||||||
def inject_todo():
|
def inject_todo():
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>{% block title %}title{% endblock %} - {{ config.USER_APP_NAME }}</title>
|
<title>{% block title %}title{% endblock %} - {{ config.USER_APP_NAME }}</title>
|
||||||
<link rel="stylesheet" type="text/css" href="/static/bootstrap.css">
|
<link rel="stylesheet" type="text/css" href="/static/bootstrap.css">
|
||||||
<link rel="stylesheet" type="text/css" href="/static/custom.css?v=20">
|
<link rel="stylesheet" type="text/css" href="/static/custom.css?v=21">
|
||||||
<link rel="search" type="application/opensearchdescription+xml" href="/static/opensearch.xml" title="ContentDB" />
|
<link rel="search" type="application/opensearchdescription+xml" href="/static/opensearch.xml" title="ContentDB" />
|
||||||
<link rel="shortcut icon" href="/favicon-16.png" sizes="16x16">
|
<link rel="shortcut icon" href="/favicon-16.png" sizes="16x16">
|
||||||
<link rel="icon" href="/favicon-128.png" sizes="128x128">
|
<link rel="icon" href="/favicon-128.png" sizes="128x128">
|
||||||
|
@ -5,9 +5,44 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block container %}
|
{% block container %}
|
||||||
<main class="container mt-4 content">
|
|
||||||
{% if not page["no_h1"] %}<h1>{{ page['title'] }}</h1>{% endif %}
|
|
||||||
|
|
||||||
{{ page.html | safe }}
|
{% set html = page.html %}
|
||||||
</main>
|
{% if page.meta.get("toc", True) %}
|
||||||
|
<div class="container mt-4">
|
||||||
|
<main class="row">
|
||||||
|
<article class="col-md-9 content">
|
||||||
|
{% if not page["no_h1"] %}<h1>{{ page['title'] }}</h1>{% endif %}
|
||||||
|
|
||||||
|
{{ html | safe }}
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<nav class="col-md-3 toc">
|
||||||
|
{% set headings = get_headings(html) %}
|
||||||
|
<ul class="nav flex-column" role="menu">
|
||||||
|
{% for item in headings recursive %}
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="#{{ item.link }}">
|
||||||
|
{{ item.text }}
|
||||||
|
</a>
|
||||||
|
{% if item.children %}
|
||||||
|
<ul class="nav flex-column" role="menu">
|
||||||
|
{{ loop(item.children) }}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="container mt-4">
|
||||||
|
<article class="content">
|
||||||
|
{% if not page["no_h1"] %}<h1>{{ page['title'] }}</h1>{% endif %}
|
||||||
|
|
||||||
|
{{ html | safe }}
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -3,6 +3,7 @@ from functools import partial
|
|||||||
import bleach
|
import bleach
|
||||||
from bleach import Cleaner
|
from bleach import Cleaner
|
||||||
from bleach.linkifier import LinkifyFilter
|
from bleach.linkifier import LinkifyFilter
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
from markdown import Markdown
|
from markdown import Markdown
|
||||||
from flask import Markup
|
from flask import Markup
|
||||||
|
|
||||||
@ -40,6 +41,10 @@ def allow_class(_tag, name, value):
|
|||||||
return name == "class" and value in ALLOWED_CSS
|
return name == "class" and value in ALLOWED_CSS
|
||||||
|
|
||||||
ALLOWED_ATTRIBUTES = {
|
ALLOWED_ATTRIBUTES = {
|
||||||
|
"h1": ["id"],
|
||||||
|
"h2": ["id"],
|
||||||
|
"h3": ["id"],
|
||||||
|
"h4": ["id"],
|
||||||
"a": ["href", "title"],
|
"a": ["href", "title"],
|
||||||
"img": ["src", "title", "alt"],
|
"img": ["src", "title", "alt"],
|
||||||
"code": allow_class,
|
"code": allow_class,
|
||||||
@ -51,6 +56,7 @@ ALLOWED_PROTOCOLS = ["http", "https", "mailto"]
|
|||||||
|
|
||||||
md = None
|
md = None
|
||||||
|
|
||||||
|
|
||||||
def render_markdown(source):
|
def render_markdown(source):
|
||||||
html = md.convert(source)
|
html = md.convert(source)
|
||||||
|
|
||||||
@ -61,6 +67,7 @@ def render_markdown(source):
|
|||||||
filters=[partial(LinkifyFilter, callbacks=bleach.linkifier.DEFAULT_CALLBACKS)])
|
filters=[partial(LinkifyFilter, callbacks=bleach.linkifier.DEFAULT_CALLBACKS)])
|
||||||
return cleaner.clean(html)
|
return cleaner.clean(html)
|
||||||
|
|
||||||
|
|
||||||
def init_app(app):
|
def init_app(app):
|
||||||
global md
|
global md
|
||||||
|
|
||||||
@ -69,3 +76,26 @@ def init_app(app):
|
|||||||
@app.template_filter()
|
@app.template_filter()
|
||||||
def markdown(source):
|
def markdown(source):
|
||||||
return Markup(render_markdown(source))
|
return Markup(render_markdown(source))
|
||||||
|
|
||||||
|
|
||||||
|
def get_headings(html: str):
|
||||||
|
soup = BeautifulSoup(html, "html.parser")
|
||||||
|
headings = soup.find_all(["h1", "h2", "h3"])
|
||||||
|
|
||||||
|
root = []
|
||||||
|
stack = []
|
||||||
|
for heading in headings:
|
||||||
|
this = { "link": heading.get("id") or "", "text": heading.text, "children": [] }
|
||||||
|
this_level = int(heading.name[1:]) - 1
|
||||||
|
|
||||||
|
while this_level <= len(stack):
|
||||||
|
stack.pop()
|
||||||
|
|
||||||
|
if len(stack) > 0:
|
||||||
|
stack[-1]["children"].append(this)
|
||||||
|
else:
|
||||||
|
root.append(this)
|
||||||
|
|
||||||
|
stack.append(this)
|
||||||
|
|
||||||
|
return root
|
Loading…
Reference in New Issue
Block a user