mirror of
https://github.com/minetest/contentdb.git
synced 2025-01-08 22:17:34 +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_github import GitHub
|
||||
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_login import logout_user, current_user, LoginManager
|
||||
import os, redis
|
||||
@ -29,7 +29,7 @@ import os, redis
|
||||
app = Flask(__name__, static_folder="public/static")
|
||||
app.config["FLATPAGES_ROOT"] = "flatpages"
|
||||
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"] = {
|
||||
"fenced_code": {},
|
||||
"tables": {},
|
||||
@ -69,7 +69,7 @@ if not app.debug and app.config["MAIL_UTILS_ERROR_SEND_TO"]:
|
||||
app.logger.addHandler(build_handler(app))
|
||||
|
||||
|
||||
from .markdown import init_app
|
||||
from app.utils.markdown import init_app
|
||||
init_app(app)
|
||||
|
||||
# @babel.localeselector
|
||||
|
@ -21,7 +21,7 @@ from flask_wtf import FlaskForm
|
||||
from wtforms import *
|
||||
from wtforms.validators import *
|
||||
|
||||
from app.markdown import render_markdown
|
||||
from app.utils.markdown import render_markdown
|
||||
from app.models import *
|
||||
from app.tasks.emails import send_user_email
|
||||
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 app import csrf
|
||||
from app.markdown import render_markdown
|
||||
from app.models import Tag, PackageState, PackageType, Package, db, PackageRelease, Tags, Permission, ForumTopic, MinetestRelease, APIToken, PackageScreenshot
|
||||
from app.utils.markdown import render_markdown
|
||||
from app.models import Tag, PackageState, PackageType, Package, db, PackageRelease, Permission, ForumTopic, MinetestRelease, APIToken, PackageScreenshot
|
||||
from app.querybuilder import QueryBuilder
|
||||
from app.utils import is_package_page
|
||||
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.utils import randomString, login_user_set_active
|
||||
from app.tasks.forumtasks import checkForumAccount
|
||||
from app.tasks.phpbbparser import getProfile
|
||||
from app.utils.phpbbparser import getProfile
|
||||
import re
|
||||
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
title: Help
|
||||
toc: False
|
||||
|
||||
## 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/).
|
||||
|
||||
## Endpoints
|
||||
|
||||
### Misc
|
||||
|
||||
* GET `/api/whoami/` - JSON dictionary with the following keys:
|
||||
* `is_authenticated` - True on successful API authentication
|
||||
* `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.
|
||||
|
||||
### Packages
|
||||
|
||||
## Packages
|
||||
|
||||
* GET `/api/packages/` - 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
|
||||
* `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)
|
||||
* 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"
|
||||
```
|
||||
|
||||
### Screenshots
|
||||
|
||||
## Screenshots
|
||||
|
||||
* GET `/api/packages/<username>/<name>/screenshots/` (List)
|
||||
* 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]"
|
||||
```
|
||||
|
||||
### Topics
|
||||
|
||||
## Topics
|
||||
|
||||
* GET `/api/topics/` - Supports [Package Queries](#package-queries), and the following two options:
|
||||
* `show_added` - Show topics which exist as packages, default true.
|
||||
* `show_discarded` - Show topics which have been marked as outdated, default false.
|
||||
|
||||
### Minetest
|
||||
|
||||
* 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
|
||||
### Topic Queries
|
||||
|
||||
Example:
|
||||
|
||||
@ -178,3 +172,8 @@ Supported query parameters:
|
||||
* `show_added` - Show topics that have an existing package.
|
||||
* `show_discarded` - Show topics marked as discarded.
|
||||
* `limit` - Return at most `limit` topics.
|
||||
|
||||
|
||||
## Minetest
|
||||
|
||||
* GET `/api/minetest_versions/`
|
||||
|
@ -148,3 +148,24 @@ blockquote {
|
||||
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
|
||||
from app.models import *
|
||||
from app.tasks import celery
|
||||
from .phpbbparser import getProfile, getTopicsFromForum
|
||||
from app.utils.phpbbparser import getProfile, getTopicsFromForum
|
||||
import urllib.request
|
||||
|
||||
@celery.task()
|
||||
|
@ -6,6 +6,9 @@ from flask_babel import format_timedelta, gettext
|
||||
from urllib.parse import urlparse
|
||||
from datetime import datetime as dt
|
||||
|
||||
from .utils.markdown import get_headings
|
||||
|
||||
|
||||
@app.context_processor
|
||||
def inject_debug():
|
||||
return dict(debug=app.debug)
|
||||
@ -13,7 +16,9 @@ def inject_debug():
|
||||
@app.context_processor
|
||||
def inject_functions():
|
||||
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
|
||||
def inject_todo():
|
||||
|
@ -7,7 +7,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<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/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="shortcut icon" href="/favicon-16.png" sizes="16x16">
|
||||
<link rel="icon" href="/favicon-128.png" sizes="128x128">
|
||||
|
@ -5,9 +5,44 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block container %}
|
||||
<main class="container mt-4 content">
|
||||
{% if not page["no_h1"] %}<h1>{{ page['title'] }}</h1>{% endif %}
|
||||
|
||||
{{ page.html | safe }}
|
||||
</main>
|
||||
{% set html = page.html %}
|
||||
{% 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 %}
|
||||
|
@ -3,6 +3,7 @@ from functools import partial
|
||||
import bleach
|
||||
from bleach import Cleaner
|
||||
from bleach.linkifier import LinkifyFilter
|
||||
from bs4 import BeautifulSoup
|
||||
from markdown import Markdown
|
||||
from flask import Markup
|
||||
|
||||
@ -40,6 +41,10 @@ def allow_class(_tag, name, value):
|
||||
return name == "class" and value in ALLOWED_CSS
|
||||
|
||||
ALLOWED_ATTRIBUTES = {
|
||||
"h1": ["id"],
|
||||
"h2": ["id"],
|
||||
"h3": ["id"],
|
||||
"h4": ["id"],
|
||||
"a": ["href", "title"],
|
||||
"img": ["src", "title", "alt"],
|
||||
"code": allow_class,
|
||||
@ -51,6 +56,7 @@ ALLOWED_PROTOCOLS = ["http", "https", "mailto"]
|
||||
|
||||
md = None
|
||||
|
||||
|
||||
def render_markdown(source):
|
||||
html = md.convert(source)
|
||||
|
||||
@ -61,6 +67,7 @@ def render_markdown(source):
|
||||
filters=[partial(LinkifyFilter, callbacks=bleach.linkifier.DEFAULT_CALLBACKS)])
|
||||
return cleaner.clean(html)
|
||||
|
||||
|
||||
def init_app(app):
|
||||
global md
|
||||
|
||||
@ -69,3 +76,26 @@ def init_app(app):
|
||||
@app.template_filter()
|
||||
def 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