drf-to-mkdoc 0.1.5__py3-none-any.whl → 0.1.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of drf-to-mkdoc might be problematic. Click here for more details.
- drf_to_mkdoc/__init__.py +1 -1
- drf_to_mkdoc/apps.py +6 -2
- drf_to_mkdoc/conf/defaults.py +0 -1
- drf_to_mkdoc/conf/settings.py +11 -5
- drf_to_mkdoc/management/commands/build_docs.py +61 -19
- drf_to_mkdoc/management/commands/generate_docs.py +5 -5
- drf_to_mkdoc/management/commands/generate_model_docs.py +37 -7
- drf_to_mkdoc/static/drf-to-mkdoc/javascripts/endpoints-filter.js +189 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/accessibility.css +21 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/animations.css +11 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/badges.css +54 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/base.css +84 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/endpoint-content.css +165 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/endpoints-grid.css +194 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/filter-section.css +209 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/fixes.css +44 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/layout.css +31 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/loading.css +35 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/responsive.css +96 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/sections.css +35 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/stats.css +34 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/tags.css +92 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/theme-toggle.css +42 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/variables.css +73 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/models/animations.css +25 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/models/base.css +83 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/models/model-cards.css +126 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/models/model-tables.css +57 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/models/responsive.css +51 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/models/variables.css +46 -0
- drf_to_mkdoc/utils/common.py +31 -29
- drf_to_mkdoc/utils/{endpoint_generator.py → endpoint_detail_generator.py} +214 -384
- drf_to_mkdoc/utils/endpoint_list_generator.py +234 -0
- drf_to_mkdoc/utils/extractors/query_parameter_extractors.py +15 -16
- drf_to_mkdoc/utils/{model_generator.py → model_detail_generator.py} +20 -51
- drf_to_mkdoc/utils/model_list_generator.py +67 -0
- {drf_to_mkdoc-0.1.5.dist-info → drf_to_mkdoc-0.1.8.dist-info}/METADATA +3 -25
- drf_to_mkdoc-0.1.8.dist-info/RECORD +50 -0
- drf_to_mkdoc-0.1.5.dist-info/RECORD +0 -25
- {drf_to_mkdoc-0.1.5.dist-info → drf_to_mkdoc-0.1.8.dist-info}/WHEEL +0 -0
- {drf_to_mkdoc-0.1.5.dist-info → drf_to_mkdoc-0.1.8.dist-info}/licenses/LICENSE +0 -0
- {drf_to_mkdoc-0.1.5.dist-info → drf_to_mkdoc-0.1.8.dist-info}/top_level.txt +0 -0
drf_to_mkdoc/__init__.py
CHANGED
drf_to_mkdoc/apps.py
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
1
3
|
from django.apps import AppConfig
|
|
2
4
|
|
|
5
|
+
logger = logging.getLogger()
|
|
6
|
+
|
|
3
7
|
|
|
4
8
|
class DrfToMkdocConfig(AppConfig):
|
|
5
9
|
default_auto_field = "django.db.models.BigAutoField"
|
|
6
10
|
name = "drf_to_mkdoc"
|
|
7
11
|
verbose_name = "DRF to MkDocs Documentation Generator"
|
|
8
|
-
|
|
12
|
+
|
|
9
13
|
def ready(self):
|
|
10
14
|
"""Initialize the app when Django starts."""
|
|
11
15
|
# Import management commands to register them
|
|
12
16
|
try:
|
|
13
17
|
import drf_to_mkdoc.management.commands # noqa
|
|
14
18
|
except ImportError:
|
|
15
|
-
|
|
19
|
+
logger.exception("Failed to import drf_to_mkdoc commands")
|
drf_to_mkdoc/conf/defaults.py
CHANGED
|
@@ -5,7 +5,6 @@ DEFAULTS = {
|
|
|
5
5
|
"MODEL_DOCS_FILE": "docs/model-docs.json", # Path to model documentation JSON file
|
|
6
6
|
"DOC_CONFIG_FILE": "docs/configs/doc_config.json", # Path to documentation configuration file
|
|
7
7
|
"CUSTOM_SCHEMA_FILE": "docs/configs/custom_schema.json", # Path to custom schema file
|
|
8
|
-
|
|
9
8
|
# Django apps - required, no default
|
|
10
9
|
"DJANGO_APPS": None, # List of Django app names to process
|
|
11
10
|
}
|
drf_to_mkdoc/conf/settings.py
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
from django.conf import settings
|
|
2
|
+
|
|
2
3
|
from drf_to_mkdoc.conf.defaults import DEFAULTS
|
|
3
4
|
|
|
5
|
+
|
|
4
6
|
class DRFToMkDocSettings:
|
|
5
7
|
required_settings = ["DJANGO_APPS"]
|
|
8
|
+
project_settings = {"PROJECT_NAME": "drf-to-mkdoc"}
|
|
6
9
|
|
|
7
10
|
def __init__(self, user_settings_key="DRF_TO_MKDOC", defaults=None):
|
|
8
11
|
self.user_settings_key = user_settings_key
|
|
@@ -11,16 +14,18 @@ class DRFToMkDocSettings:
|
|
|
11
14
|
|
|
12
15
|
def get(self, key):
|
|
13
16
|
if key not in self.defaults:
|
|
17
|
+
if key in self.project_settings:
|
|
18
|
+
return self.project_settings[key]
|
|
14
19
|
raise AttributeError(f"Invalid DRF_TO_MKDOC setting: '{key}'")
|
|
15
|
-
|
|
20
|
+
|
|
16
21
|
value = self._user_settings.get(key, self.defaults[key])
|
|
17
|
-
|
|
22
|
+
|
|
18
23
|
if value is None and key in self.required_settings:
|
|
19
24
|
raise ValueError(
|
|
20
25
|
f"DRF_TO_MKDOC setting '{key}' is required but not configured. "
|
|
21
26
|
f"Please add it to your Django settings under {self.user_settings_key}."
|
|
22
27
|
)
|
|
23
|
-
|
|
28
|
+
|
|
24
29
|
return value
|
|
25
30
|
|
|
26
31
|
def __getattr__(self, key):
|
|
@@ -28,17 +33,18 @@ class DRFToMkDocSettings:
|
|
|
28
33
|
|
|
29
34
|
def validate_required_settings(self):
|
|
30
35
|
missing_settings = []
|
|
31
|
-
|
|
36
|
+
|
|
32
37
|
for setting in self.required_settings:
|
|
33
38
|
try:
|
|
34
39
|
self.get(setting)
|
|
35
40
|
except ValueError:
|
|
36
41
|
missing_settings.append(setting)
|
|
37
|
-
|
|
42
|
+
|
|
38
43
|
if missing_settings:
|
|
39
44
|
raise ValueError(
|
|
40
45
|
f"Missing required settings: {', '.join(missing_settings)}. "
|
|
41
46
|
f"Please configure these in your Django settings under {self.user_settings_key}."
|
|
42
47
|
)
|
|
43
48
|
|
|
49
|
+
|
|
44
50
|
drf_to_mkdoc_settings = DRFToMkDocSettings(defaults=DEFAULTS)
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
import shutil
|
|
1
2
|
import subprocess
|
|
2
3
|
from pathlib import Path
|
|
3
4
|
|
|
5
|
+
from django.apps import apps
|
|
4
6
|
from django.conf import settings
|
|
7
|
+
from django.core.management import call_command
|
|
5
8
|
from django.core.management.base import BaseCommand, CommandError
|
|
6
|
-
|
|
9
|
+
|
|
7
10
|
from drf_to_mkdoc.conf.settings import drf_to_mkdoc_settings
|
|
8
|
-
from django.core.management import call_command
|
|
9
11
|
|
|
10
12
|
|
|
11
13
|
class Command(BaseCommand):
|
|
@@ -18,13 +20,13 @@ class Command(BaseCommand):
|
|
|
18
20
|
try:
|
|
19
21
|
apps.check_apps_ready()
|
|
20
22
|
except Exception as e:
|
|
21
|
-
raise CommandError(f"Django apps not properly configured: {e}")
|
|
23
|
+
raise CommandError(f"Django apps not properly configured: {e}") from e
|
|
22
24
|
|
|
23
25
|
base_dir = Path(settings.BASE_DIR)
|
|
24
26
|
site_dir = base_dir / "site"
|
|
25
27
|
mkdocs_config = base_dir / "mkdocs.yml"
|
|
26
28
|
mkdocs_config_alt = base_dir / "mkdocs.yaml"
|
|
27
|
-
|
|
29
|
+
|
|
28
30
|
if not mkdocs_config.exists() and not mkdocs_config_alt.exists():
|
|
29
31
|
raise CommandError(
|
|
30
32
|
"MkDocs configuration file not found. Please create either 'mkdocs.yml' or 'mkdocs.yaml' "
|
|
@@ -34,10 +36,7 @@ class Command(BaseCommand):
|
|
|
34
36
|
try:
|
|
35
37
|
# Generate the model documentation JSON first
|
|
36
38
|
self.stdout.write("Generating model documentation...")
|
|
37
|
-
|
|
38
|
-
call_command(
|
|
39
|
-
"generate_model_docs", "--pretty"
|
|
40
|
-
)
|
|
39
|
+
call_command("generate_model_docs", "--pretty")
|
|
41
40
|
self.stdout.write(self.style.SUCCESS("Model documentation generated."))
|
|
42
41
|
|
|
43
42
|
# Generate the documentation content
|
|
@@ -47,17 +46,7 @@ class Command(BaseCommand):
|
|
|
47
46
|
|
|
48
47
|
# Build the MkDocs site
|
|
49
48
|
self.stdout.write("Building MkDocs site...")
|
|
50
|
-
|
|
51
|
-
["mkdocs", "build", "--clean"],
|
|
52
|
-
check=False,
|
|
53
|
-
cwd=base_dir,
|
|
54
|
-
capture_output=True,
|
|
55
|
-
text=True,
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
if result.returncode != 0:
|
|
59
|
-
raise CommandError(f"MkDocs build failed: {result.stderr}")
|
|
60
|
-
|
|
49
|
+
self._build_mkdocs_site(base_dir, site_dir)
|
|
61
50
|
self.stdout.write(self.style.SUCCESS("Documentation built successfully!"))
|
|
62
51
|
self.stdout.write(f"Site built in: {site_dir}")
|
|
63
52
|
|
|
@@ -65,3 +54,56 @@ class Command(BaseCommand):
|
|
|
65
54
|
raise CommandError(
|
|
66
55
|
"MkDocs not found. Please install it with: pip install mkdocs mkdocs-material"
|
|
67
56
|
) from e
|
|
57
|
+
|
|
58
|
+
def _build_mkdocs_site(self, base_dir: Path, site_dir: Path) -> None:
|
|
59
|
+
"""
|
|
60
|
+
Build the MkDocs site with proper security checks.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
base_dir: The base directory of the Django project
|
|
64
|
+
site_dir: The directory where the site will be built
|
|
65
|
+
|
|
66
|
+
Raises:
|
|
67
|
+
FileNotFoundError: If mkdocs executable is not found
|
|
68
|
+
CommandError: If the build process fails
|
|
69
|
+
"""
|
|
70
|
+
mkdocs_path = shutil.which("mkdocs")
|
|
71
|
+
if not mkdocs_path:
|
|
72
|
+
raise FileNotFoundError("mkdocs executable not found in PATH")
|
|
73
|
+
|
|
74
|
+
mkdocs_path_obj = Path(mkdocs_path)
|
|
75
|
+
if not mkdocs_path_obj.exists() or not mkdocs_path_obj.is_file():
|
|
76
|
+
raise CommandError(f"Invalid mkdocs executable path: {mkdocs_path}")
|
|
77
|
+
|
|
78
|
+
if not base_dir.is_absolute():
|
|
79
|
+
base_dir = base_dir.resolve()
|
|
80
|
+
|
|
81
|
+
if not base_dir.exists():
|
|
82
|
+
raise CommandError(f"Base directory does not exist: {base_dir}")
|
|
83
|
+
|
|
84
|
+
cmd = [
|
|
85
|
+
str(mkdocs_path_obj), # Convert to string for subprocess
|
|
86
|
+
"build",
|
|
87
|
+
"--clean",
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
result = subprocess.run( # noqa S603
|
|
92
|
+
cmd,
|
|
93
|
+
check=True,
|
|
94
|
+
cwd=str(base_dir),
|
|
95
|
+
capture_output=True,
|
|
96
|
+
text=True,
|
|
97
|
+
timeout=300,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
if result.stdout:
|
|
101
|
+
self.stdout.write(f"MkDocs output: {result.stdout}")
|
|
102
|
+
|
|
103
|
+
except subprocess.TimeoutExpired as e:
|
|
104
|
+
raise CommandError("MkDocs build timed out after 5 minutes") from e
|
|
105
|
+
except subprocess.CalledProcessError as e:
|
|
106
|
+
error_msg = f"MkDocs build failed (exit code {e.returncode})"
|
|
107
|
+
if e.stderr:
|
|
108
|
+
error_msg += f": {e.stderr}"
|
|
109
|
+
raise CommandError(error_msg) from e
|
|
@@ -4,15 +4,15 @@ from pathlib import Path
|
|
|
4
4
|
|
|
5
5
|
from django.core.management.base import BaseCommand
|
|
6
6
|
|
|
7
|
+
from drf_to_mkdoc.conf.settings import drf_to_mkdoc_settings
|
|
7
8
|
from drf_to_mkdoc.utils.common import get_schema, load_model_json_data
|
|
8
|
-
from drf_to_mkdoc.utils.
|
|
9
|
-
create_endpoints_index,
|
|
9
|
+
from drf_to_mkdoc.utils.endpoint_detail_generator import (
|
|
10
10
|
generate_endpoint_files,
|
|
11
11
|
parse_endpoints_from_schema,
|
|
12
12
|
)
|
|
13
|
-
from drf_to_mkdoc.utils.
|
|
14
|
-
from drf_to_mkdoc.
|
|
15
|
-
|
|
13
|
+
from drf_to_mkdoc.utils.endpoint_list_generator import create_endpoints_index
|
|
14
|
+
from drf_to_mkdoc.utils.model_detail_generator import generate_model_docs
|
|
15
|
+
from drf_to_mkdoc.utils.model_list_generator import create_models_index
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
class Command(BaseCommand):
|
|
@@ -9,6 +9,7 @@ from django.db import models
|
|
|
9
9
|
|
|
10
10
|
from drf_to_mkdoc.conf.settings import drf_to_mkdoc_settings
|
|
11
11
|
|
|
12
|
+
|
|
12
13
|
class Command(BaseCommand):
|
|
13
14
|
help = "Generate model documentation JSON from Django model introspection"
|
|
14
15
|
|
|
@@ -139,21 +140,50 @@ class Command(BaseCommand):
|
|
|
139
140
|
|
|
140
141
|
def introspect_relationship(self, field):
|
|
141
142
|
"""Introspect relationship fields"""
|
|
142
|
-
|
|
143
|
+
# Safely resolve related model label; can be None for generic relations
|
|
144
|
+
related_model_label = None
|
|
145
|
+
try:
|
|
146
|
+
if getattr(field, "related_model", None) is not None:
|
|
147
|
+
related_model_label = (
|
|
148
|
+
f"{field.related_model._meta.app_label}.{field.related_model.__name__}"
|
|
149
|
+
)
|
|
150
|
+
except Exception:
|
|
151
|
+
related_model_label = None
|
|
152
|
+
|
|
153
|
+
relationship_data = {
|
|
143
154
|
"name": field.name,
|
|
144
155
|
"type": field.__class__.__name__,
|
|
145
|
-
"related_model":
|
|
146
|
-
f"{field.related_model.__name__}",
|
|
156
|
+
"related_model": related_model_label,
|
|
147
157
|
"related_name": getattr(field, "related_name", None),
|
|
148
158
|
"on_delete": self.get_on_delete_name(field),
|
|
149
159
|
"null": getattr(field, "null", False),
|
|
150
160
|
"blank": getattr(field, "blank", False),
|
|
151
|
-
"many_to_many": field
|
|
152
|
-
"one_to_many": field
|
|
153
|
-
"many_to_one": field
|
|
154
|
-
"one_to_one": field
|
|
161
|
+
"many_to_many": getattr(field, "many_to_many", False),
|
|
162
|
+
"one_to_many": getattr(field, "one_to_many", False),
|
|
163
|
+
"many_to_one": getattr(field, "many_to_one", False),
|
|
164
|
+
"one_to_one": getattr(field, "one_to_one", False),
|
|
155
165
|
}
|
|
156
166
|
|
|
167
|
+
# Handle Django generic relations where related_model can be None
|
|
168
|
+
field_class_name = field.__class__.__name__
|
|
169
|
+
if field_class_name in ("GenericForeignKey", "GenericRelation"):
|
|
170
|
+
relationship_data["is_generic"] = True
|
|
171
|
+
# Capture common generic relation details when available
|
|
172
|
+
for attr_name in (
|
|
173
|
+
"ct_field",
|
|
174
|
+
"fk_field",
|
|
175
|
+
"object_id_field",
|
|
176
|
+
"content_type_field",
|
|
177
|
+
"for_concrete_model",
|
|
178
|
+
"related_query_name",
|
|
179
|
+
):
|
|
180
|
+
if hasattr(field, attr_name):
|
|
181
|
+
relationship_data[attr_name] = getattr(field, attr_name)
|
|
182
|
+
else:
|
|
183
|
+
relationship_data["is_generic"] = False
|
|
184
|
+
|
|
185
|
+
return relationship_data
|
|
186
|
+
|
|
157
187
|
def get_on_delete_name(self, field):
|
|
158
188
|
"""Get readable name for on_delete option"""
|
|
159
189
|
if not hasattr(field, "on_delete") or field.on_delete is None:
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
let currentFilters = {
|
|
2
|
+
method: '',
|
|
3
|
+
path: '',
|
|
4
|
+
models: '',
|
|
5
|
+
auth: '',
|
|
6
|
+
roles: '',
|
|
7
|
+
contentType: '',
|
|
8
|
+
params: '',
|
|
9
|
+
schema: '',
|
|
10
|
+
pagination: '',
|
|
11
|
+
tags: '',
|
|
12
|
+
app: '',
|
|
13
|
+
ordering: '',
|
|
14
|
+
search: ''
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function applyFilters() {
|
|
18
|
+
// Read all filters
|
|
19
|
+
currentFilters = {
|
|
20
|
+
method: getValue('filter-method'),
|
|
21
|
+
path: getValue('filter-path'),
|
|
22
|
+
models: getValue('filter-models'),
|
|
23
|
+
auth: getValue('filter-auth'),
|
|
24
|
+
roles: getValue('filter-roles'),
|
|
25
|
+
contentType: getValue('filter-content-type'),
|
|
26
|
+
params: getValue('filter-params'),
|
|
27
|
+
schema: getValue('filter-schema'),
|
|
28
|
+
pagination: getValue('filter-pagination'),
|
|
29
|
+
tags: getValue('filter-tags'),
|
|
30
|
+
app: getValue('filter-app'),
|
|
31
|
+
ordering: getValue('filter-ordering'),
|
|
32
|
+
search: getValue('filter-search'),
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
updateURLParams(currentFilters);
|
|
36
|
+
|
|
37
|
+
const cards = document.querySelectorAll('.endpoint-card');
|
|
38
|
+
let visibleCount = 0;
|
|
39
|
+
|
|
40
|
+
cards.forEach(card => {
|
|
41
|
+
const visible = matchesFilters(card);
|
|
42
|
+
card.classList.toggle('hidden', !visible);
|
|
43
|
+
if (visible) visibleCount++;
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Collapse viewset sections with no visible cards
|
|
47
|
+
document.querySelectorAll('.viewset-section').forEach(section => {
|
|
48
|
+
const visibleCards = section.querySelectorAll('.endpoint-card:not(.hidden)');
|
|
49
|
+
section.style.display = visibleCards.length === 0 ? 'none' : '';
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Collapse app sections with no visible viewsets
|
|
53
|
+
document.querySelectorAll('.app-section').forEach(app => {
|
|
54
|
+
const visibleViewsets = app.querySelectorAll('.viewset-section:not([style*="display: none"])');
|
|
55
|
+
app.style.display = visibleViewsets.length === 0 ? 'none' : '';
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Update filter result stats
|
|
59
|
+
document.querySelector('.filter-results').textContent =
|
|
60
|
+
`Showing ${visibleCount} of ${cards.length} endpoints`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function getValue(id) {
|
|
64
|
+
const el = document.getElementById(id);
|
|
65
|
+
return el ? el.value.trim().toLowerCase() : '';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function populateAppFilterOptions() {
|
|
69
|
+
const select = document.getElementById('filter-app');
|
|
70
|
+
const apps = new Set();
|
|
71
|
+
|
|
72
|
+
document.querySelectorAll('.endpoint-card').forEach(card => {
|
|
73
|
+
const app = card.dataset.app;
|
|
74
|
+
if (app) apps.add(app);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Convert to sorted array and add as options
|
|
78
|
+
Array.from(apps).sort().forEach(app => {
|
|
79
|
+
const opt = document.createElement('option');
|
|
80
|
+
opt.value = app;
|
|
81
|
+
opt.textContent = app;
|
|
82
|
+
select.appendChild(opt);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function matchesFilters(card) {
|
|
87
|
+
const d = card.dataset;
|
|
88
|
+
const f = currentFilters;
|
|
89
|
+
|
|
90
|
+
if (f.method && d.method !== f.method) return false;
|
|
91
|
+
if (f.path && !d.path.includes(f.path)) return false;
|
|
92
|
+
if (f.app && d.app !== f.app) return false;
|
|
93
|
+
if (f.auth && d.auth !== f.auth) return false;
|
|
94
|
+
if (f.pagination && d.pagination !== f.pagination) return false;
|
|
95
|
+
if (f.search && d.search !== f.search) return false;
|
|
96
|
+
if (f.ordering && d.ordering !== f.ordering) return false;
|
|
97
|
+
if (f.models && !d.models.includes(f.models)) return false;
|
|
98
|
+
if (f.roles && !d.roles.includes(f.roles)) return false;
|
|
99
|
+
if (f.tags && !d.tags.includes(f.tags)) return false;
|
|
100
|
+
if (f.contentType && d.contentType !== f.contentType) return false;
|
|
101
|
+
|
|
102
|
+
if (f.params && !d.params.includes(f.params)) return false;
|
|
103
|
+
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function clearFilters() {
|
|
108
|
+
document.querySelectorAll('.filter-input, .filter-select').forEach(el => el.value = '');
|
|
109
|
+
currentFilters = {
|
|
110
|
+
method: '', path: '', models: '', auth: '', roles: '', contentType: '',
|
|
111
|
+
params: '', schema: '', pagination: '', tags: '', app: '', ordering: '', search: ''
|
|
112
|
+
};
|
|
113
|
+
applyFilters();
|
|
114
|
+
updateURLParams(currentFilters);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
function updateURLParams(filters) {
|
|
119
|
+
const params = new URLSearchParams();
|
|
120
|
+
Object.entries(filters).forEach(([k, v]) => {
|
|
121
|
+
if (v) params.set(k, v);
|
|
122
|
+
});
|
|
123
|
+
history.replaceState(null, '', '?' + params.toString());
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function loadURLParams() {
|
|
127
|
+
const params = new URLSearchParams(location.search);
|
|
128
|
+
params.forEach((v, k) => {
|
|
129
|
+
const input = document.getElementById(`filter-${k}`);
|
|
130
|
+
if (input) input.value = v;
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
135
|
+
populateAppFilterOptions();
|
|
136
|
+
loadURLParams();
|
|
137
|
+
document.querySelectorAll('.filter-input, .filter-select').forEach(input => {
|
|
138
|
+
input.addEventListener('input', debounce(applyFilters, 250));
|
|
139
|
+
});
|
|
140
|
+
applyFilters();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
function debounce(func, delay) {
|
|
144
|
+
let timeout;
|
|
145
|
+
return function () {
|
|
146
|
+
clearTimeout(timeout);
|
|
147
|
+
timeout = setTimeout(func, delay);
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
154
|
+
// Example filter implementation
|
|
155
|
+
const container = document.getElementById('fullscreen-container');
|
|
156
|
+
|
|
157
|
+
// Add filter controls
|
|
158
|
+
const filterControls = document.createElement('div');
|
|
159
|
+
filterControls.className = 'filter-controls';
|
|
160
|
+
filterControls.innerHTML = `
|
|
161
|
+
<select id="filter-select">
|
|
162
|
+
<option value="none">No Filter</option>
|
|
163
|
+
<option value="grayscale">Grayscale</option>
|
|
164
|
+
<option value="sepia">Sepia</option>
|
|
165
|
+
<option value="blur">Blur</option>
|
|
166
|
+
</select>
|
|
167
|
+
`;
|
|
168
|
+
container.prepend(filterControls);
|
|
169
|
+
|
|
170
|
+
// Apply filter based on selection
|
|
171
|
+
document.getElementById('filter-select').addEventListener('change', function(e) {
|
|
172
|
+
container.style.filter = e.target.value === 'none' ? '' : e.target.value + '(100%)';
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// Your custom filter logic here
|
|
176
|
+
// Example: Apply initial filter if needed
|
|
177
|
+
// container.style.filter = 'grayscale(50%)';
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
181
|
+
const filterPanel = document.getElementById('filterSidebar');
|
|
182
|
+
const leftSidebar = document.querySelector('.md-sidebar--primary');
|
|
183
|
+
|
|
184
|
+
if (filterPanel && leftSidebar) {
|
|
185
|
+
leftSidebar.innerHTML = ''; // Remove nav if not needed
|
|
186
|
+
leftSidebar.appendChild(filterPanel);
|
|
187
|
+
filterPanel.classList.remove('collapsed'); // Make sure it's visible
|
|
188
|
+
}
|
|
189
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/* ===== ACCESSIBILITY ===== */
|
|
2
|
+
@media (prefers-contrast: high) {
|
|
3
|
+
:root {
|
|
4
|
+
--border-color: #000000;
|
|
5
|
+
--text-secondary: #000000;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
[data-theme="dark"],
|
|
9
|
+
[data-md-color-scheme="slate"] {
|
|
10
|
+
--border-color: #ffffff;
|
|
11
|
+
--text-secondary: #ffffff;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
@media (prefers-reduced-motion: reduce) {
|
|
16
|
+
* {
|
|
17
|
+
animation-duration: 0.01ms !important;
|
|
18
|
+
animation-iteration-count: 1 !important;
|
|
19
|
+
transition-duration: 0.01ms !important;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/* ===== ANIMATIONS ===== */
|
|
2
|
+
.filter-highlight {
|
|
3
|
+
background: linear-gradient(135deg, rgba(59, 130, 246, 0.1), rgba(147, 51, 234, 0.1));
|
|
4
|
+
border-color: var(--accent-primary) !important;
|
|
5
|
+
animation: highlight-pulse 2s ease-in-out;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
@keyframes highlight-pulse {
|
|
9
|
+
0%, 100% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.4); }
|
|
10
|
+
50% { box-shadow: 0 0 0 10px rgba(59, 130, 246, 0); }
|
|
11
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/* ===== METHOD BADGES ===== */
|
|
2
|
+
.method-badge {
|
|
3
|
+
position: relative;
|
|
4
|
+
padding: 6px 12px;
|
|
5
|
+
font-size: 12px;
|
|
6
|
+
font-weight: bold;
|
|
7
|
+
border-radius: 20px;
|
|
8
|
+
color: white;
|
|
9
|
+
text-transform: uppercase;
|
|
10
|
+
overflow: hidden;
|
|
11
|
+
min-width: 60px;
|
|
12
|
+
text-align: center;
|
|
13
|
+
flex-shrink: 0;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.method-badge::before {
|
|
17
|
+
content: '';
|
|
18
|
+
position: absolute;
|
|
19
|
+
top: 0;
|
|
20
|
+
left: -100%;
|
|
21
|
+
width: 100%;
|
|
22
|
+
height: 100%;
|
|
23
|
+
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
|
|
24
|
+
transition: left 0.5s ease;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.method-badge:hover::before {
|
|
28
|
+
left: 100%;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.method-get {
|
|
32
|
+
background: linear-gradient(135deg, #10b981, #059669);
|
|
33
|
+
box-shadow: 0 4px 15px rgba(16, 185, 129, 0.3);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.method-post {
|
|
37
|
+
background: linear-gradient(135deg, #f59e0b, #d97706);
|
|
38
|
+
box-shadow: 0 4px 15px rgba(245, 158, 11, 0.3);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.method-put {
|
|
42
|
+
background: linear-gradient(135deg, #8b5cf6, #7c3aed);
|
|
43
|
+
box-shadow: 0 4px 15px rgba(139, 92, 246, 0.3);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.method-patch {
|
|
47
|
+
background: linear-gradient(135deg, #06b6d4, #0891b2);
|
|
48
|
+
box-shadow: 0 4px 15px rgba(6, 182, 212, 0.3);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.method-delete {
|
|
52
|
+
background: linear-gradient(135deg, #ef4444, #dc2626);
|
|
53
|
+
box-shadow: 0 4px 15px rgba(239, 68, 68, 0.3);
|
|
54
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/* ===== BASE STYLES ===== */
|
|
2
|
+
* {
|
|
3
|
+
box-sizing: border-box;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
body {
|
|
7
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
8
|
+
background: var(--bg-primary);
|
|
9
|
+
color: var(--text-primary);
|
|
10
|
+
line-height: 1.6;
|
|
11
|
+
transition: background-color 0.3s ease, color 0.3s ease;
|
|
12
|
+
margin: 0;
|
|
13
|
+
padding: 0;
|
|
14
|
+
min-height: 100vh;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.md-nav__item--active > .md-nav__link {
|
|
18
|
+
font-weight: 600;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/* Admonition customizations */
|
|
22
|
+
.admonition.note {
|
|
23
|
+
border-left-color: var(--md-primary-fg-color);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.admonition.tip {
|
|
27
|
+
border-left-color: #00c853;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.admonition.warning {
|
|
31
|
+
border-left-color: #ff9800;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.admonition.danger {
|
|
35
|
+
border-left-color: #f44336;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* Search result enhancements */
|
|
39
|
+
.md-search-result__meta {
|
|
40
|
+
color: var(--md-default-fg-color--light);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* Code syntax highlighting enhancements */
|
|
44
|
+
.codehilite .k { color: #0066cc; } /* Keywords */
|
|
45
|
+
.codehilite .s { color: #009900; } /* Strings */
|
|
46
|
+
.codehilite .c { color: #999999; } /* Comments */
|
|
47
|
+
.codehilite .n { color: #333333; } /* Names */
|
|
48
|
+
.codehilite .o { color: #666666; } /* Operators */
|
|
49
|
+
|
|
50
|
+
/* Custom icons for different sections */
|
|
51
|
+
.md-nav__item[data-md-level="1"] > .md-nav__link[href*="models"] {
|
|
52
|
+
position: relative;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.md-nav__item[data-md-level="1"] > .md-nav__link[href*="models"]::before {
|
|
56
|
+
content: "🗃️";
|
|
57
|
+
margin-right: 0.5rem;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.md-nav__item[data-md-level="1"] > .md-nav__link[href*="endpoints"] {
|
|
61
|
+
position: relative;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.md-nav__item[data-md-level="1"] > .md-nav__link[href*="endpoints"]::before {
|
|
65
|
+
content: "🔗";
|
|
66
|
+
margin-right: 0.5rem;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* Print styles */
|
|
70
|
+
@media print {
|
|
71
|
+
.md-header, .md-sidebar, .md-footer {
|
|
72
|
+
display: none;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.md-content {
|
|
76
|
+
margin: 0;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.highlight pre {
|
|
80
|
+
border: 1px solid #ccc;
|
|
81
|
+
page-break-inside: avoid;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|